first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Extensionmanager\Command;
|
||||
|
||||
/*
|
||||
* 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 Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
|
||||
use TYPO3\CMS\Extensionmanager\Utility\InstallUtility;
|
||||
|
||||
/**
|
||||
* Command for activating an existing extension via CLI.
|
||||
*/
|
||||
class ActivateExtensionCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Defines the allowed options for this command
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription('Activates an extension by key')
|
||||
->setHelp('The extension files must be present in one of the recognized extension folder paths in TYPO3.')
|
||||
->setAliases(['extensionmanager:extension:install', 'extension:install'])
|
||||
->addArgument(
|
||||
'extensionkey',
|
||||
InputArgument::REQUIRED,
|
||||
'The extension key of a currently deactivated extension, located in one of TYPO3\'s extension paths.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs an extension
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$extensionKey = $input->getArgument('extensionkey');
|
||||
|
||||
// Ensure the _cli_ user is authenticated because the extension might import data
|
||||
Bootstrap::initializeBackendAuthentication();
|
||||
|
||||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
// Emits packages may have changed signal
|
||||
$signalSlotDispatcher = $objectManager->get(Dispatcher::class);
|
||||
$signalSlotDispatcher->dispatch('PackageManagement', 'packagesMayHaveChanged');
|
||||
|
||||
// Do the installation process
|
||||
$objectManager->get(InstallUtility::class)->install($extensionKey);
|
||||
|
||||
$io->success('Activated extension ' . $extensionKey . ' successfully.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Extensionmanager\Command;
|
||||
|
||||
/*
|
||||
* 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 Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extensionmanager\Utility\InstallUtility;
|
||||
|
||||
/**
|
||||
* Command for deactivating an extension via CLI.
|
||||
*/
|
||||
class DeactivateExtensionCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Defines the allowed options for this command
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription('Deactivates an extension by extension key')
|
||||
->setAliases(['extensionmanager:extension:uninstall', 'extension:uninstall'])
|
||||
->addArgument(
|
||||
'extensionkey',
|
||||
InputArgument::REQUIRED,
|
||||
'The extension key of a currently activated extension.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs an extension
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$extensionKey = $input->getArgument('extensionkey');
|
||||
|
||||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
$objectManager->get(InstallUtility::class)->uninstall($extensionKey);
|
||||
|
||||
$io->success('Deactivated extension "' . $extensionKey . '" successfully.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Command;
|
||||
|
||||
/*
|
||||
* 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\ClassLoadingInformation;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\CommandController;
|
||||
|
||||
/**
|
||||
* CommandController for working with extension management through CLI/scheduler
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use Symfony Command alternatives instead.
|
||||
*/
|
||||
class ExtensionCommandController extends CommandController
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $requestAdminPermissions = true;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
|
||||
*/
|
||||
protected $signalSlotDispatcher;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
|
||||
*/
|
||||
public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
|
||||
{
|
||||
$this->signalSlotDispatcher = $signalSlotDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs an extension by key
|
||||
*
|
||||
* The extension files must be present in one of the
|
||||
* recognised extension folder paths in TYPO3.
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @cli
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use the equivalent Symfony Command instead.
|
||||
*/
|
||||
public function installCommand($extensionKey)
|
||||
{
|
||||
trigger_error('Calling ExtensionCommandController->installCommand() will be removed in TYPO3 v10.0. Use the Symfony command "extension:activate" instead, to be called via the "typo3" CLI entrypoint.', E_USER_DEPRECATED);
|
||||
$this->emitPackagesMayHaveChangedSignal();
|
||||
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $service */
|
||||
$service = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class);
|
||||
$service->install($extensionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls an extension by key
|
||||
*
|
||||
* The extension files must be present in one of the
|
||||
* recognised extension folder paths in TYPO3.
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @cli
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use the equivalent Symfony Command instead.
|
||||
*/
|
||||
public function uninstallCommand($extensionKey)
|
||||
{
|
||||
trigger_error('Calling ExtensionCommandController->uninstallCommand() will be removed in TYPO3 v10.0. Use the Symfony command "extension:deactivate" instead, to be called via the "typo3" CLI entrypoint.', E_USER_DEPRECATED);
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $service */
|
||||
$service = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class);
|
||||
$service->uninstall($extensionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates class loading information.
|
||||
*
|
||||
* This command is only needed during development. The extension manager takes care
|
||||
* creating or updating this info properly during extension (de-)activation.
|
||||
*
|
||||
* @cli
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use the equivalent Symfony Command instead.
|
||||
*/
|
||||
public function dumpClassLoadingInformationCommand()
|
||||
{
|
||||
trigger_error('Calling ExtensionCommandController->dumpClassLoadingInformationCommand() will be removed in TYPO3 v10.0. Use the Symfony command "dumpautoload" instead, to be called via the "typo3" CLI entrypoint.', E_USER_DEPRECATED);
|
||||
if (Environment::isComposerMode()) {
|
||||
$this->output->outputLine('<error>Class loading information is managed by composer. Use "composer dump-autoload" command to update the information.</error>');
|
||||
$this->quit(1);
|
||||
} else {
|
||||
ClassLoadingInformation::dumpClassLoadingInformation();
|
||||
$this->output->outputLine('Class Loading information has been updated.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits packages may have changed signal
|
||||
*/
|
||||
protected function emitPackagesMayHaveChangedSignal()
|
||||
{
|
||||
$this->signalSlotDispatcher->dispatch('PackageManagement', 'packagesMayHaveChanged');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract action controller.
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class AbstractController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
|
||||
{
|
||||
const TRIGGER_RefreshModuleMenu = 'refreshModuleMenu';
|
||||
|
||||
const TRIGGER_RefreshTopbar = 'refreshTopbar';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $triggerArguments = [
|
||||
self::TRIGGER_RefreshModuleMenu,
|
||||
self::TRIGGER_RefreshTopbar
|
||||
];
|
||||
|
||||
/**
|
||||
* Translation shortcut
|
||||
*
|
||||
* @param $key
|
||||
* @param array|null $arguments
|
||||
* @return string|null
|
||||
*/
|
||||
protected function translate($key, $arguments = null)
|
||||
{
|
||||
return \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate($key, 'extensionmanager', $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles trigger arguments, e.g. refreshing the module menu
|
||||
* widget if an extension with backend modules has been enabled
|
||||
* or disabled.
|
||||
*/
|
||||
protected function handleTriggerArguments()
|
||||
{
|
||||
$triggers = [];
|
||||
|
||||
foreach ($this->triggerArguments as $triggerArgument) {
|
||||
if ($this->request->hasArgument($triggerArgument)) {
|
||||
$triggers[$triggerArgument] = $this->request->getArgument($triggerArgument);
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->assign('triggers', $triggers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use TYPO3\CMS\Backend\View\BackendTemplateView;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
|
||||
|
||||
/**
|
||||
* Abstract action controller.
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class AbstractModuleController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* BackendTemplateContainer
|
||||
*
|
||||
* @var BackendTemplateView
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* Backend Template Container
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultViewObjectName = BackendTemplateView::class;
|
||||
|
||||
/**
|
||||
* Resolve view and initialize the general view-variables extensionName,
|
||||
* controllerName and actionName based on the request object
|
||||
*
|
||||
* @return \TYPO3\CMS\Fluid\View\TemplateView
|
||||
*/
|
||||
protected function resolveView()
|
||||
{
|
||||
$view = parent::resolveView();
|
||||
$view->assignMultiple([
|
||||
'extensionName' => $this->request->getControllerExtensionName(),
|
||||
'controllerName' => $this->request->getControllerName(),
|
||||
'actionName' => $this->request->getControllerActionName()
|
||||
]);
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the action menu
|
||||
*/
|
||||
protected function generateMenu()
|
||||
{
|
||||
$menuItems = [
|
||||
'installedExtensions' => [
|
||||
'controller' => 'List',
|
||||
'action' => 'index',
|
||||
'label' => $this->translate('installedExtensions')
|
||||
]
|
||||
];
|
||||
|
||||
if (!$this->settings['offlineMode'] && !Environment::isComposerMode()) {
|
||||
$menuItems['getExtensions'] = [
|
||||
'controller' => 'List',
|
||||
'action' => 'ter',
|
||||
'label' => $this->translate('getExtensions')
|
||||
];
|
||||
$menuItems['distributions'] = [
|
||||
'controller' => 'List',
|
||||
'action' => 'distributions',
|
||||
'label' => $this->translate('distributions')
|
||||
];
|
||||
|
||||
if ($this->actionMethodName === 'showAllVersionsAction') {
|
||||
$menuItems['showAllVersions'] = [
|
||||
'controller' => 'List',
|
||||
'action' => 'showAllVersions',
|
||||
'label' => $this->translate('showAllVersions') . ' ' . $this->request->getArgument('extensionKey')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$uriBuilder = $this->objectManager->get(UriBuilder::class);
|
||||
$uriBuilder->setRequest($this->request);
|
||||
|
||||
$menu = $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
|
||||
$menu->setIdentifier('ExtensionManagerModuleMenu');
|
||||
|
||||
foreach ($menuItems as $menuItemConfig) {
|
||||
if ($this->request->getControllerName() === $menuItemConfig['controller']) {
|
||||
$isActive = $this->request->getControllerActionName() === $menuItemConfig['action'] ? true : false;
|
||||
} else {
|
||||
$isActive = false;
|
||||
}
|
||||
$menuItem = $menu->makeMenuItem()
|
||||
->setTitle($menuItemConfig['label'])
|
||||
->setHref($this->getHref($menuItemConfig['controller'], $menuItemConfig['action']))
|
||||
->setActive($isActive);
|
||||
$menu->addMenuItem($menuItem);
|
||||
}
|
||||
|
||||
$this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
|
||||
$this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates te URI for a backend action
|
||||
*
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param array $parameters
|
||||
* @return string
|
||||
*/
|
||||
protected function getHref($controller, $action, $parameters = [])
|
||||
{
|
||||
$uriBuilder = $this->objectManager->get(UriBuilder::class);
|
||||
$uriBuilder->setRequest($this->request);
|
||||
return $uriBuilder->reset()->uriFor($action, $parameters, $controller);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Controller for handling extension related actions like
|
||||
* installing, removing, downloading of data or files
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ActionController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility
|
||||
*/
|
||||
protected $installUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
|
||||
*/
|
||||
protected $fileHandlingUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility
|
||||
*/
|
||||
protected $extensionModelUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService
|
||||
*/
|
||||
protected $managementService;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility
|
||||
*/
|
||||
public function injectInstallUtility(\TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility)
|
||||
{
|
||||
$this->installUtility = $installUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility
|
||||
*/
|
||||
public function injectFileHandlingUtility(\TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility)
|
||||
{
|
||||
$this->fileHandlingUtility = $fileHandlingUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility $extensionModelUtility
|
||||
*/
|
||||
public function injectExtensionModelUtility(\TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility $extensionModelUtility)
|
||||
{
|
||||
$this->extensionModelUtility = $extensionModelUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService
|
||||
*/
|
||||
public function injectManagementService(\TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService)
|
||||
{
|
||||
$this->managementService = $managementService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle extension installation state action
|
||||
*
|
||||
* @param string $extensionKey
|
||||
*/
|
||||
protected function toggleExtensionInstallationStateAction($extensionKey)
|
||||
{
|
||||
$installedExtensions = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getLoadedExtensionListArray();
|
||||
try {
|
||||
if (in_array($extensionKey, $installedExtensions)) {
|
||||
// uninstall
|
||||
$this->installUtility->uninstall($extensionKey);
|
||||
} else {
|
||||
// install
|
||||
$extension = $this->extensionModelUtility->mapExtensionArrayToModel(
|
||||
$this->installUtility->enrichExtensionWithDetails($extensionKey, false)
|
||||
);
|
||||
if ($this->managementService->installExtension($extension) === false) {
|
||||
$this->redirect('unresolvedDependencies', 'List', null, ['extensionKey' => $extensionKey]);
|
||||
}
|
||||
}
|
||||
} catch (\TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException $e) {
|
||||
$this->addFlashMessage($e->getMessage(), '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
|
||||
} catch (\TYPO3\CMS\Core\Package\Exception\PackageStatesFileNotWritableException $e) {
|
||||
$this->addFlashMessage($e->getMessage(), '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
|
||||
}
|
||||
$this->redirect('index', 'List', null, [
|
||||
self::TRIGGER_RefreshModuleMenu => true,
|
||||
self::TRIGGER_RefreshTopbar => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install an extension and omit dependency checking
|
||||
*
|
||||
* @param string $extensionKey
|
||||
*/
|
||||
public function installExtensionWithoutSystemDependencyCheckAction($extensionKey)
|
||||
{
|
||||
$this->managementService->setSkipDependencyCheck(true);
|
||||
$this->forward('toggleExtensionInstallationState', null, null, ['extensionKey' => $extensionKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an extension (if it is still installed, uninstall it first)
|
||||
*
|
||||
* @param string $extension
|
||||
* @return string
|
||||
*/
|
||||
protected function removeExtensionAction($extension)
|
||||
{
|
||||
try {
|
||||
if (\TYPO3\CMS\Core\Core\Environment::isComposerMode()) {
|
||||
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(
|
||||
'The system is set to composer mode. You are not allowed to remove any extension.',
|
||||
1590314046
|
||||
);
|
||||
}
|
||||
|
||||
$this->installUtility->removeExtension($extension);
|
||||
$this->addFlashMessage(
|
||||
\TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate(
|
||||
'extensionList.remove.message',
|
||||
'extensionmanager',
|
||||
[
|
||||
'extension' => $extension,
|
||||
]
|
||||
)
|
||||
);
|
||||
} catch (\TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException $e) {
|
||||
$this->addFlashMessage($e->getMessage(), '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
|
||||
} catch (\TYPO3\CMS\Core\Package\Exception $e) {
|
||||
$this->addFlashMessage($e->getMessage(), '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an extension as a zip file
|
||||
*
|
||||
* @param string $extension
|
||||
*/
|
||||
protected function downloadExtensionZipAction($extension)
|
||||
{
|
||||
$fileName = $this->fileHandlingUtility->createZipFileFromExtension($extension);
|
||||
$this->fileHandlingUtility->sendZipFileToBrowserAndDelete($fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the static SQL data of an extension
|
||||
*
|
||||
* @param string $extension
|
||||
*/
|
||||
protected function reloadExtensionDataAction($extension)
|
||||
{
|
||||
$extension = $this->installUtility->enrichExtensionWithDetails($extension, false);
|
||||
$registryKey = $extension['siteRelPath'] . 'ext_tables_static+adt.sql';
|
||||
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$registry->remove('extensionDataImport', $registryKey);
|
||||
|
||||
$this->installUtility->processExtensionSetup($extension['key']);
|
||||
|
||||
$this->redirect('index', 'List');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\View\BackendTemplateView;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
|
||||
|
||||
/**
|
||||
* Controller for distribution related actions
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class DistributionController extends AbstractModuleController
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Package\PackageManager
|
||||
*/
|
||||
protected $packageManager;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Core\Package\PackageManager $packageManager
|
||||
*/
|
||||
public function injectPackageManager(\TYPO3\CMS\Core\Package\PackageManager $packageManager)
|
||||
{
|
||||
$this->packageManager = $packageManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the doc header properly here
|
||||
*
|
||||
* @param ViewInterface $view
|
||||
*/
|
||||
protected function initializeView(ViewInterface $view)
|
||||
{
|
||||
if ($view instanceof BackendTemplateView) {
|
||||
/** @var BackendTemplateView $view */
|
||||
parent::initializeView($view);
|
||||
$this->generateMenu();
|
||||
$this->registerDocheaderButtons();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows information about the distribution
|
||||
*
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
|
||||
*/
|
||||
public function showAction(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension)
|
||||
{
|
||||
$extensionKey = $extension->getExtensionKey();
|
||||
// Check if extension/package is installed
|
||||
$active = $this->packageManager->isPackageActive($extensionKey);
|
||||
|
||||
$this->view->assign('distributionActive', $active);
|
||||
$this->view->assign('extension', $extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the Icons into the docheader
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function registerDocheaderButtons()
|
||||
{
|
||||
/** @var ButtonBar $buttonBar */
|
||||
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
|
||||
|
||||
$uriBuilder = $this->controllerContext->getUriBuilder();
|
||||
|
||||
$uri = $uriBuilder->reset()->uriFor('distributions', [], 'List');
|
||||
$title = $this->translate('extConfTemplate.backToList');
|
||||
$icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL);
|
||||
$button = $buttonBar->makeLinkButton()
|
||||
->setHref($uri)
|
||||
->setTitle($title)
|
||||
->setIcon($icon);
|
||||
$buttonBar->addButton($button, ButtonBar::BUTTON_POSITION_LEFT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
|
||||
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
|
||||
|
||||
/**
|
||||
* Controller for actions related to the TER download of an extension
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class DownloadController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
|
||||
*/
|
||||
protected $extensionRepository;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
|
||||
*/
|
||||
protected $fileHandlingUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService
|
||||
*/
|
||||
protected $managementService;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility
|
||||
*/
|
||||
protected $installUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\DownloadUtility
|
||||
*/
|
||||
protected $downloadUtility;
|
||||
|
||||
/**
|
||||
* @var JsonView
|
||||
*/
|
||||
protected $defaultViewObjectName = JsonView::class;
|
||||
|
||||
/**
|
||||
* @var JsonView
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @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\FileHandlingUtility $fileHandlingUtility
|
||||
*/
|
||||
public function injectFileHandlingUtility(\TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility)
|
||||
{
|
||||
$this->fileHandlingUtility = $fileHandlingUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService
|
||||
*/
|
||||
public function injectManagementService(\TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService)
|
||||
{
|
||||
$this->managementService = $managementService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility
|
||||
*/
|
||||
public function injectInstallUtility(\TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility)
|
||||
{
|
||||
$this->installUtility = $installUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\DownloadUtility $downloadUtility
|
||||
*/
|
||||
public function injectDownloadUtility(\TYPO3\CMS\Extensionmanager\Utility\DownloadUtility $downloadUtility)
|
||||
{
|
||||
$this->downloadUtility = $downloadUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines which view object should be used for the installFromTer action
|
||||
*/
|
||||
protected function initializeInstallFromTerAction()
|
||||
{
|
||||
$this->defaultViewObjectName = \TYPO3\CMS\Fluid\View\TemplateView::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check extension dependencies
|
||||
*
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function checkDependenciesAction(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension)
|
||||
{
|
||||
$message = '';
|
||||
$title = '';
|
||||
$hasDependencies = false;
|
||||
$hasErrors = false;
|
||||
$dependencyTypes = null;
|
||||
$configuration = [
|
||||
'value' => [
|
||||
'dependencies' => [],
|
||||
],
|
||||
];
|
||||
$isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
|
||||
if (!$isAutomaticInstallationEnabled) {
|
||||
// if automatic installation is deactivated, no dependency check is needed (download only)
|
||||
$action = 'installExtensionWithoutSystemDependencyCheck';
|
||||
} else {
|
||||
$action = 'installFromTer';
|
||||
try {
|
||||
$dependencyTypes = $this->managementService->getAndResolveDependencies($extension);
|
||||
if (!empty($dependencyTypes)) {
|
||||
$hasDependencies = true;
|
||||
$message = '<p>' . $this->translate('downloadExtension.dependencies.headline') . '</p>';
|
||||
foreach ($dependencyTypes as $dependencyType => $dependencies) {
|
||||
$extensions = '';
|
||||
foreach ($dependencies as $extensionKey => $dependency) {
|
||||
if (!isset($configuration['value']['dependencies'][$dependencyType])) {
|
||||
$configuration['value']['dependencies'][$dependencyType] = [];
|
||||
}
|
||||
$configuration['value']['dependencies'][$dependencyType][$extensionKey] = [
|
||||
'_exclude' => [
|
||||
'categoryIndexFromStringOrNumber',
|
||||
],
|
||||
];
|
||||
$extensions .= $this->translate(
|
||||
'downloadExtension.dependencies.extensionWithVersion',
|
||||
[
|
||||
$extensionKey, $dependency->getVersion()
|
||||
]
|
||||
) . '<br />';
|
||||
}
|
||||
$message .= $this->translate(
|
||||
'downloadExtension.dependencies.typeHeadline',
|
||||
[
|
||||
$this->translate('downloadExtension.dependencyType.' . $dependencyType),
|
||||
$extensions
|
||||
]
|
||||
);
|
||||
}
|
||||
$title = $this->translate('downloadExtension.dependencies.resolveAutomatically');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$hasErrors = true;
|
||||
$title = $this->translate('downloadExtension.dependencies.errorTitle');
|
||||
$message = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
$url = $this->uriBuilder->uriFor(
|
||||
$action,
|
||||
['extension' => $extension->getUid(), 'format' => 'json'],
|
||||
'Download'
|
||||
);
|
||||
$this->view->setConfiguration($configuration);
|
||||
$this->view->assign('value', [
|
||||
'dependencies' => $dependencyTypes,
|
||||
'url' => $url,
|
||||
'message' => $message,
|
||||
'hasErrors' => $hasErrors,
|
||||
'hasDependencies' => $hasDependencies,
|
||||
'title' => $title
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install an extension from TER action
|
||||
*
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
|
||||
* @param string $downloadPath
|
||||
*/
|
||||
public function installFromTerAction(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension, $downloadPath = 'Local')
|
||||
{
|
||||
list($result, $errorMessages) = $this->installFromTer($extension, $downloadPath);
|
||||
$isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
|
||||
$this->view
|
||||
->assign('result', $result)
|
||||
->assign('extension', $extension)
|
||||
->assign('installationTypeLanguageKey', $isAutomaticInstallationEnabled ? '' : '.downloadOnly')
|
||||
->assign('unresolvedDependencies', $errorMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check extension dependencies with special dependencies
|
||||
*
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function installExtensionWithoutSystemDependencyCheckAction(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension)
|
||||
{
|
||||
$this->managementService->setSkipDependencyCheck(true);
|
||||
$this->forward('installFromTer', null, null, ['extension' => $extension, 'downloadPath' => 'Local']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action for installing a distribution -
|
||||
* redirects directly to configuration after installing
|
||||
*
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
|
||||
*/
|
||||
public function installDistributionAction(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension)
|
||||
{
|
||||
if (!\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('impexp')) {
|
||||
$this->forward('distributions', 'List');
|
||||
}
|
||||
list($result, $errorMessages) = $this->installFromTer($extension);
|
||||
if ($errorMessages) {
|
||||
foreach ($errorMessages as $extensionKey => $messages) {
|
||||
foreach ($messages as $message) {
|
||||
$this->addFlashMessage(
|
||||
$message['message'],
|
||||
\TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate(
|
||||
'distribution.error.headline',
|
||||
'extensionmanager',
|
||||
[$extensionKey]
|
||||
),
|
||||
\TYPO3\CMS\Core\Messaging\AbstractMessage::ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect back to distributions list action
|
||||
$this->redirect(
|
||||
'distributions',
|
||||
'List'
|
||||
);
|
||||
} else {
|
||||
// FlashMessage that extension is installed
|
||||
$this->addFlashMessage(
|
||||
\TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate(
|
||||
'distribution.welcome.message',
|
||||
'extensionmanager',
|
||||
[$extension->getExtensionKey()]
|
||||
),
|
||||
\TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('distribution.welcome.headline', 'extensionmanager')
|
||||
);
|
||||
|
||||
// Redirect to show action
|
||||
$this->redirect(
|
||||
'show',
|
||||
'Distribution',
|
||||
null,
|
||||
['extension' => $extension]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an extension. Makes no sanity check but directly searches highest
|
||||
* available version from TER and updates. Update check is done by the list
|
||||
* already. This method should only be called if we are sure that there is
|
||||
* an update.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function updateExtensionAction()
|
||||
{
|
||||
$extensionKey = $this->request->getArgument('extension');
|
||||
$version = $this->request->getArgument('version');
|
||||
$extension = $this->extensionRepository->findOneByExtensionKeyAndVersion($extensionKey, $version);
|
||||
if (!$extension instanceof Extension) {
|
||||
$extension = $this->extensionRepository->findHighestAvailableVersion($extensionKey);
|
||||
}
|
||||
$installedExtensions = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getLoadedExtensionListArray();
|
||||
try {
|
||||
if (in_array($extensionKey, $installedExtensions, true)) {
|
||||
// To resolve new dependencies the extension is installed again
|
||||
$this->managementService->installExtension($extension);
|
||||
} else {
|
||||
$this->managementService->downloadMainExtension($extension);
|
||||
}
|
||||
$this->addFlashMessage(
|
||||
$this->translate('extensionList.updateFlashMessage.body', [$extensionKey]),
|
||||
$this->translate('extensionList.updateFlashMessage.title')
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$this->addFlashMessage($e->getMessage(), '', FlashMessage::ERROR);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show update comments for extensions that can be updated.
|
||||
* Fetches update comments for all versions between the current
|
||||
* installed and the highest version.
|
||||
*/
|
||||
protected function updateCommentForUpdatableVersionsAction()
|
||||
{
|
||||
$extensionKey = $this->request->getArgument('extension');
|
||||
$versionStart = $this->request->getArgument('integerVersionStart');
|
||||
$versionStop = $this->request->getArgument('integerVersionStop');
|
||||
$updateComments = [];
|
||||
/** @var Extension[] $updatableVersions */
|
||||
$updatableVersions = $this->extensionRepository->findByVersionRangeAndExtensionKeyOrderedByVersion(
|
||||
$extensionKey,
|
||||
$versionStart,
|
||||
$versionStop,
|
||||
false
|
||||
);
|
||||
$highestPossibleVersion = false;
|
||||
|
||||
foreach ($updatableVersions as $updatableVersion) {
|
||||
if ($highestPossibleVersion === false) {
|
||||
$highestPossibleVersion = $updatableVersion->getVersion();
|
||||
}
|
||||
$updateComments[$updatableVersion->getVersion()] = $updatableVersion->getUpdateComment();
|
||||
}
|
||||
|
||||
$this->view->assign('value', [
|
||||
'updateComments' => $updateComments,
|
||||
'url' => $this->uriBuilder->uriFor(
|
||||
'updateExtension',
|
||||
['extension' => $extensionKey, 'version' => $highestPossibleVersion]
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install an extension from TER
|
||||
* Downloads the extension, resolves dependencies and installs it
|
||||
*
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
|
||||
* @param string $downloadPath
|
||||
* @return array
|
||||
*/
|
||||
protected function installFromTer(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension, $downloadPath = 'Local')
|
||||
{
|
||||
$result = false;
|
||||
$errorMessages = [];
|
||||
try {
|
||||
$this->downloadUtility->setDownloadPath($downloadPath);
|
||||
$isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
|
||||
$this->managementService->setAutomaticInstallationEnabled($isAutomaticInstallationEnabled);
|
||||
if (($result = $this->managementService->installExtension($extension)) === false) {
|
||||
$errorMessages = $this->managementService->getDependencyErrors();
|
||||
}
|
||||
} catch (\TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException $e) {
|
||||
$errorMessages = [
|
||||
$extension->getExtensionKey() => [
|
||||
[
|
||||
'code' => $e->getCode(),
|
||||
'message' => $e->getMessage(),
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [$result, $errorMessages];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\View\BackendTemplateView;
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
|
||||
use TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility;
|
||||
use TYPO3\CMS\Extensionmanager\Utility\Repository\Helper;
|
||||
|
||||
/**
|
||||
* Controller for extension listings (TER or local extensions)
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ListController extends AbstractModuleController
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
|
||||
*/
|
||||
protected $extensionRepository;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
|
||||
*/
|
||||
protected $listUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Page\PageRenderer
|
||||
*/
|
||||
protected $pageRenderer;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility
|
||||
*/
|
||||
protected $dependencyUtility;
|
||||
|
||||
/**
|
||||
* @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\Core\Page\PageRenderer $pageRenderer
|
||||
*/
|
||||
public function injectPageRenderer(\TYPO3\CMS\Core\Page\PageRenderer $pageRenderer)
|
||||
{
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility
|
||||
*/
|
||||
public function injectDependencyUtility(\TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility)
|
||||
{
|
||||
$this->dependencyUtility = $dependencyUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the needed JavaScript files for all actions
|
||||
*/
|
||||
public function initializeAction()
|
||||
{
|
||||
$this->pageRenderer->addInlineLanguageLabelFile('EXT:extensionmanager/Resources/Private/Language/locallang.xlf');
|
||||
$isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode');
|
||||
if ($isAutomaticInstallationEnabled) {
|
||||
$this->settings['offlineMode'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the doc header properly here
|
||||
*
|
||||
* @param ViewInterface $view
|
||||
*/
|
||||
protected function initializeView(ViewInterface $view)
|
||||
{
|
||||
if ($view instanceof BackendTemplateView) {
|
||||
/** @var BackendTemplateView $view */
|
||||
parent::initializeView($view);
|
||||
$this->generateMenu();
|
||||
$this->registerDocheaderButtons();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an information about composer mode
|
||||
*/
|
||||
protected function addComposerModeNotification()
|
||||
{
|
||||
if (Environment::isComposerMode()) {
|
||||
$this->addFlashMessage(
|
||||
LocalizationUtility::translate(
|
||||
'composerMode.message',
|
||||
'extensionmanager'
|
||||
),
|
||||
LocalizationUtility::translate(
|
||||
'composerMode.title',
|
||||
'extensionmanager'
|
||||
),
|
||||
FlashMessage::INFO
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows list of extensions present in the system
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->addComposerModeNotification();
|
||||
$availableAndInstalledExtensions = $this->listUtility->getAvailableAndInstalledExtensionsWithAdditionalInformation();
|
||||
ksort($availableAndInstalledExtensions);
|
||||
$this->view->assignMultiple(
|
||||
[
|
||||
'extensions' => $availableAndInstalledExtensions,
|
||||
'isComposerMode' => Environment::isComposerMode(),
|
||||
]
|
||||
);
|
||||
$this->handleTriggerArguments();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a list of unresolved dependency errors with the possibility to bypass the dependency check
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @throws ExtensionManagerException
|
||||
*/
|
||||
public function unresolvedDependenciesAction($extensionKey)
|
||||
{
|
||||
$availableExtensions = $this->listUtility->getAvailableExtensions();
|
||||
if (isset($availableExtensions[$extensionKey])) {
|
||||
$extensionArray = $this->listUtility->enrichExtensionsWithEmConfAndTerInformation(
|
||||
[
|
||||
$extensionKey => $availableExtensions[$extensionKey]
|
||||
]
|
||||
);
|
||||
/** @var ExtensionModelUtility $extensionModelUtility */
|
||||
$extensionModelUtility = $this->objectManager->get(ExtensionModelUtility::class);
|
||||
$extension = $extensionModelUtility->mapExtensionArrayToModel($extensionArray[$extensionKey]);
|
||||
} else {
|
||||
throw new ExtensionManagerException('Extension ' . $extensionKey . ' is not available', 1402421007);
|
||||
}
|
||||
$this->dependencyUtility->checkDependencies($extension);
|
||||
$this->view->assign('extension', $extension);
|
||||
$this->view->assign('unresolvedDependencies', $this->dependencyUtility->getDependencyErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows extensions from TER
|
||||
* Either all extensions or depending on a search param
|
||||
*
|
||||
* @param string $search
|
||||
*/
|
||||
public function terAction($search = '')
|
||||
{
|
||||
$this->addComposerModeNotification();
|
||||
$search = trim($search);
|
||||
if (!empty($search)) {
|
||||
$extensions = $this->extensionRepository->findByTitleOrAuthorNameOrExtensionKey($search);
|
||||
} else {
|
||||
$extensions = $this->extensionRepository->findAll();
|
||||
}
|
||||
$availableAndInstalledExtensions = $this->listUtility->getAvailableAndInstalledExtensions($this->listUtility->getAvailableExtensions());
|
||||
$this->view->assign('extensions', $extensions)
|
||||
->assign('search', $search)
|
||||
->assign('availableAndInstalled', $availableAndInstalledExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action for listing all possible distributions
|
||||
*
|
||||
* @param bool $showUnsuitableDistributions
|
||||
*/
|
||||
public function distributionsAction($showUnsuitableDistributions = false)
|
||||
{
|
||||
$this->addComposerModeNotification();
|
||||
$importExportInstalled = ExtensionManagementUtility::isLoaded('impexp');
|
||||
if ($importExportInstalled) {
|
||||
try {
|
||||
/** @var Helper $repositoryHelper */
|
||||
$repositoryHelper = $this->objectManager->get(Helper::class);
|
||||
// Check if a TER update has been done at all, if not, fetch it directly
|
||||
// Repository needs an update, but not because of the extension hash has changed
|
||||
$isExtListUpdateNecessary = $repositoryHelper->isExtListUpdateNecessary();
|
||||
if ($isExtListUpdateNecessary > 0 && ($isExtListUpdateNecessary & $repositoryHelper::PROBLEM_EXTENSION_HASH_CHANGED) === 0) {
|
||||
$repositoryHelper->updateExtList();
|
||||
}
|
||||
} catch (ExtensionManagerException $e) {
|
||||
$this->addFlashMessage($e->getMessage(), $e->getCode(), FlashMessage::ERROR);
|
||||
}
|
||||
|
||||
$officialDistributions = $this->extensionRepository->findAllOfficialDistributions();
|
||||
$communityDistributions = $this->extensionRepository->findAllCommunityDistributions();
|
||||
|
||||
$officialDistributions = $this->dependencyUtility->filterYoungestVersionOfExtensionList($officialDistributions->toArray(), $showUnsuitableDistributions);
|
||||
$communityDistributions = $this->dependencyUtility->filterYoungestVersionOfExtensionList($communityDistributions->toArray(), $showUnsuitableDistributions);
|
||||
|
||||
$this->view->assign('officialDistributions', $officialDistributions);
|
||||
$this->view->assign('communityDistributions', $communityDistributions);
|
||||
}
|
||||
$this->view->assign('enableDistributionsView', $importExportInstalled);
|
||||
$this->view->assign('showUnsuitableDistributions', $showUnsuitableDistributions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows all versions of a specific extension
|
||||
*
|
||||
* @param string $extensionKey
|
||||
*/
|
||||
public function showAllVersionsAction($extensionKey)
|
||||
{
|
||||
$currentVersion = $this->extensionRepository->findOneByCurrentVersionByExtensionKey($extensionKey);
|
||||
$extensions = $this->extensionRepository->findByExtensionKeyOrderedByVersion($extensionKey);
|
||||
|
||||
$this->view->assignMultiple(
|
||||
[
|
||||
'extensionKey' => $extensionKey,
|
||||
'currentVersion' => $currentVersion,
|
||||
'extensions' => $extensions
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the Icons into the docheader
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function registerDocheaderButtons()
|
||||
{
|
||||
if (Environment::isComposerMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array($this->actionMethodName, ['indexAction', 'terAction', 'showAllVersionsAction'], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var ButtonBar $buttonBar */
|
||||
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
|
||||
$uriBuilder = $this->controllerContext->getUriBuilder();
|
||||
|
||||
if ($this->actionMethodName === 'showAllVersionsAction') {
|
||||
$uri = $uriBuilder->reset()->uriFor('ter', [], 'List');
|
||||
$title = $this->translate('extConfTemplate.backToList');
|
||||
$icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL);
|
||||
$classes = '';
|
||||
} else {
|
||||
$uri = $uriBuilder->reset()->uriFor('form', [], 'UploadExtensionFile');
|
||||
$title = $this->translate('extensionList.uploadExtension');
|
||||
$icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-edit-upload', Icon::SIZE_SMALL);
|
||||
$classes = 't3js-upload';
|
||||
}
|
||||
$button = $buttonBar->makeLinkButton()
|
||||
->setHref($uri)
|
||||
->setTitle($title)
|
||||
->setClasses($classes)
|
||||
->setIcon($icon);
|
||||
$buttonBar->addButton($button, ButtonBar::BUTTON_POSITION_LEFT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
|
||||
|
||||
/**
|
||||
* Controller for actions relating to update of full extension list from TER
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class UpdateFromTerController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\Repository\Helper
|
||||
*/
|
||||
protected $repositoryHelper;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\RepositoryRepository
|
||||
*/
|
||||
protected $repositoryRepository;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
|
||||
*/
|
||||
protected $listUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
|
||||
*/
|
||||
protected $extensionRepository;
|
||||
|
||||
/**
|
||||
* @var JsonView
|
||||
*/
|
||||
protected $defaultViewObjectName = JsonView::class;
|
||||
|
||||
/**
|
||||
* @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\Domain\Repository\RepositoryRepository $repositoryRepository
|
||||
*/
|
||||
public function injectRepositoryRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\RepositoryRepository $repositoryRepository)
|
||||
{
|
||||
$this->repositoryRepository = $repositoryRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update extension list from TER
|
||||
*
|
||||
* @param bool $forceUpdateCheck
|
||||
*/
|
||||
public function updateExtensionListFromTerAction($forceUpdateCheck = false)
|
||||
{
|
||||
$updated = false;
|
||||
$errorMessage = '';
|
||||
|
||||
if ($this->extensionRepository->countAll() === 0 || $forceUpdateCheck) {
|
||||
try {
|
||||
$updated = $this->repositoryHelper->updateExtList();
|
||||
} catch (\TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException $e) {
|
||||
$errorMessage = $e->getMessage();
|
||||
}
|
||||
}
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Domain\Model\Repository $repository */
|
||||
$repository = $this->repositoryRepository->findOneTypo3OrgRepository();
|
||||
|
||||
$timeFormat = $this->getLanguageService()->sL('LLL:EXT:extensionmanager/Resources/Private/Language/locallang.xlf:extensionList.updateFromTer.lastUpdate.fullTimeFormat');
|
||||
$lastUpdateTime = $repository ? $repository->getLastUpdate() : null;
|
||||
if (null === $lastUpdateTime) {
|
||||
$lastUpdatedSince = $this->getLanguageService()->sL('LLL:EXT:extensionmanager/Resources/Private/Language/locallang.xlf:extensionList.updateFromTer.never');
|
||||
$lastUpdateTime = date($timeFormat);
|
||||
} else {
|
||||
$lastUpdatedSince = \TYPO3\CMS\Backend\Utility\BackendUtility::calcAge(
|
||||
time() - $lastUpdateTime->format('U'),
|
||||
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
|
||||
);
|
||||
$lastUpdateTime = $lastUpdateTime->format($timeFormat);
|
||||
}
|
||||
$this->view->assign('value', [
|
||||
'updated' => $updated,
|
||||
'lastUpdateTime' => $lastUpdateTime,
|
||||
'timeSinceLastUpdate' => $lastUpdatedSince,
|
||||
'errorMessage' => $errorMessage
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\View\BackendTemplateView;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
|
||||
|
||||
/**
|
||||
* Controller for configuration related actions.
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class UpdateScriptController extends AbstractModuleController
|
||||
{
|
||||
/**
|
||||
* Set up the doc header properly here
|
||||
*
|
||||
* @param ViewInterface $view
|
||||
*/
|
||||
protected function initializeView(ViewInterface $view)
|
||||
{
|
||||
if ($view instanceof BackendTemplateView) {
|
||||
/** @var BackendTemplateView $view */
|
||||
parent::initializeView($view);
|
||||
$this->generateMenu();
|
||||
$this->registerDocheaderButtons();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the content of the update script (if any).
|
||||
*
|
||||
* @param string $extensionKey Extension key
|
||||
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
|
||||
*/
|
||||
public function showAction($extensionKey)
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Utility\UpdateScriptUtility $updateScriptUtility */
|
||||
$updateScriptUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\UpdateScriptUtility::class);
|
||||
$updateScriptResult = $updateScriptUtility->executeUpdateIfNeeded($extensionKey);
|
||||
$this->view
|
||||
->assign('updateScriptResult', $updateScriptResult)
|
||||
->assign('extensionKey', $extensionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the Icons into the docheader
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function registerDocheaderButtons()
|
||||
{
|
||||
/** @var ButtonBar $buttonBar */
|
||||
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
|
||||
|
||||
$uriBuilder = $this->controllerContext->getUriBuilder();
|
||||
|
||||
$uri = $uriBuilder->reset()->uriFor('index', [], 'List');
|
||||
$title = $this->translate('extConfTemplate.backToList');
|
||||
$icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL);
|
||||
$button = $buttonBar->makeLinkButton()
|
||||
->setHref($uri)
|
||||
->setTitle($title)
|
||||
->setIcon($icon);
|
||||
$buttonBar->addButton($button, ButtonBar::BUTTON_POSITION_LEFT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Security\BlockSerializationTrait;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extensionmanager\Exception\DependencyConfigurationNotFoundException;
|
||||
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
|
||||
|
||||
/**
|
||||
* Controller for handling upload of a local extension file
|
||||
* Handles .t3x or .zip files
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class UploadExtensionFileController extends AbstractController
|
||||
{
|
||||
use BlockSerializationTrait;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
|
||||
*/
|
||||
protected $extensionRepository;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
|
||||
*/
|
||||
protected $fileHandlingUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility
|
||||
*/
|
||||
protected $terUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService
|
||||
*/
|
||||
protected $managementService;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extensionBackupPath = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $removeFromOriginalPath = false;
|
||||
|
||||
/**
|
||||
* @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\FileHandlingUtility $fileHandlingUtility
|
||||
*/
|
||||
public function injectFileHandlingUtility(\TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility)
|
||||
{
|
||||
$this->fileHandlingUtility = $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\Service\ExtensionManagementService $managementService
|
||||
*/
|
||||
public function injectManagementService(\TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService)
|
||||
{
|
||||
$this->managementService = $managementService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove backup folder before destruction
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->removeBackupFolder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render upload extension form
|
||||
*/
|
||||
public function formAction()
|
||||
{
|
||||
if (Environment::isComposerMode()) {
|
||||
throw new ExtensionManagerException(
|
||||
'Composer mode is active. You are not allowed to upload any extension file.',
|
||||
1444725828
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an uploaded file and install the matching extension
|
||||
*
|
||||
* @param bool $overwrite Overwrite existing extension if TRUE
|
||||
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
|
||||
*/
|
||||
public function extractAction($overwrite = false)
|
||||
{
|
||||
if (Environment::isComposerMode()) {
|
||||
throw new ExtensionManagerException(
|
||||
'Composer mode is active. You are not allowed to upload any extension file.',
|
||||
1444725853
|
||||
);
|
||||
}
|
||||
$file = $_FILES['tx_extensionmanager_tools_extensionmanagerextensionmanager'];
|
||||
$fileName = pathinfo($file['name']['extensionFile'], PATHINFO_BASENAME);
|
||||
try {
|
||||
// If the file name isn't valid an error will be thrown
|
||||
$this->checkFileName($fileName);
|
||||
if (!empty($file['tmp_name']['extensionFile'])) {
|
||||
$tempFile = GeneralUtility::upload_to_tempfile($file['tmp_name']['extensionFile']);
|
||||
} else {
|
||||
throw new ExtensionManagerException(
|
||||
'Creating temporary file failed. Check your upload_max_filesize and post_max_size limits.',
|
||||
1342864339
|
||||
);
|
||||
}
|
||||
$extensionData = $this->extractExtensionFromFile($tempFile, $fileName, $overwrite);
|
||||
$isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
|
||||
if (!$isAutomaticInstallationEnabled) {
|
||||
$this->addFlashMessage(
|
||||
$this->translate('extensionList.uploadFlashMessage.message', [$extensionData['extKey']]),
|
||||
$this->translate('extensionList.uploadFlashMessage.title'),
|
||||
FlashMessage::OK
|
||||
);
|
||||
} else {
|
||||
if ($this->activateExtension($extensionData['extKey'])) {
|
||||
$this->addFlashMessage(
|
||||
$this->translate('extensionList.installedFlashMessage.message', [$extensionData['extKey']]),
|
||||
'',
|
||||
FlashMessage::OK
|
||||
);
|
||||
} else {
|
||||
$this->redirect('unresolvedDependencies', 'List', null, ['extensionKey' => $extensionData['extKey']]);
|
||||
}
|
||||
}
|
||||
} catch (\TYPO3\CMS\Extbase\Mvc\Exception\StopActionException $exception) {
|
||||
throw $exception;
|
||||
} catch (DependencyConfigurationNotFoundException $exception) {
|
||||
$this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
|
||||
} catch (\Exception $exception) {
|
||||
$this->removeExtensionAndRestoreFromBackup($fileName);
|
||||
$this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
|
||||
}
|
||||
$this->redirect('index', 'List', null, [
|
||||
self::TRIGGER_RefreshModuleMenu => true,
|
||||
self::TRIGGER_RefreshTopbar => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the filename of an uploaded file
|
||||
*
|
||||
* @param string $fileName
|
||||
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
|
||||
*/
|
||||
public function checkFileName($fileName)
|
||||
{
|
||||
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
if (empty($fileName)) {
|
||||
throw new ExtensionManagerException('No file given.', 1342858852);
|
||||
}
|
||||
if ($extension !== 't3x' && $extension !== 'zip') {
|
||||
throw new ExtensionManagerException('Wrong file format "' . $extension . '" given. Allowed formats are t3x and zip.', 1342858853);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a given t3x or zip file
|
||||
*
|
||||
* @param string $uploadPath Path to existing extension file
|
||||
* @param string $fileName Filename of the uploaded file
|
||||
* @param bool $overwrite If true, extension will be replaced
|
||||
* @return array Extension data
|
||||
* @throws ExtensionManagerException
|
||||
* @throws DependencyConfigurationNotFoundException
|
||||
*/
|
||||
public function extractExtensionFromFile($uploadPath, $fileName, $overwrite)
|
||||
{
|
||||
$fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
if ($fileExtension === 't3x') {
|
||||
$extensionData = $this->getExtensionFromT3xFile($uploadPath, $overwrite);
|
||||
} else {
|
||||
$extensionData = $this->getExtensionFromZipFile($uploadPath, $fileName, $overwrite);
|
||||
}
|
||||
|
||||
return $extensionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extensionKey
|
||||
* @return bool
|
||||
*/
|
||||
public function activateExtension($extensionKey)
|
||||
{
|
||||
$this->managementService->reloadPackageInformation($extensionKey);
|
||||
$extension = $this->managementService->getExtension($extensionKey);
|
||||
return is_array($this->managementService->installExtension($extension));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a given t3x file and installs the extension
|
||||
*
|
||||
* @param string $file Path to uploaded file
|
||||
* @param bool $overwrite Overwrite existing extension if TRUE
|
||||
* @throws ExtensionManagerException
|
||||
* @throws DependencyConfigurationNotFoundException
|
||||
* @return array
|
||||
*/
|
||||
protected function getExtensionFromT3xFile($file, $overwrite = false)
|
||||
{
|
||||
$fileContent = file_get_contents($file);
|
||||
if (!$fileContent) {
|
||||
throw new ExtensionManagerException('File had no or wrong content.', 1342859339);
|
||||
}
|
||||
$extensionData = $this->terUtility->decodeExchangeData($fileContent);
|
||||
if (empty($extensionData['extKey'])) {
|
||||
throw new ExtensionManagerException('Decoding the file went wrong. No extension key found', 1342864309);
|
||||
}
|
||||
$isExtensionAvailable = $this->managementService->isAvailable($extensionData['extKey']);
|
||||
if (!$overwrite && $isExtensionAvailable) {
|
||||
throw new ExtensionManagerException($this->translate('extensionList.overwritingDisabled'), 1342864310);
|
||||
}
|
||||
if ($isExtensionAvailable) {
|
||||
$this->copyExtensionFolderToTempFolder($extensionData['extKey']);
|
||||
}
|
||||
$this->removeFromOriginalPath = true;
|
||||
$extension = $this->extensionRepository->findOneByExtensionKeyAndVersion($extensionData['extKey'], $extensionData['EM_CONF']['version']);
|
||||
$this->fileHandlingUtility->unpackExtensionFromExtensionDataArray($extensionData, $extension);
|
||||
|
||||
if (empty($extension)
|
||||
&& empty($extensionData['EM_CONF']['constraints'])
|
||||
&& !isset($extensionData['FILES']['ext_emconf.php'])
|
||||
&& !isset($extensionData['FILES']['/ext_emconf.php'])
|
||||
) {
|
||||
throw new DependencyConfigurationNotFoundException('Extension cannot be installed automatically because no dependencies could be found! Please check dependencies manually (on typo3.org) before installing the extension.', 1439587168);
|
||||
}
|
||||
|
||||
return $extensionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a given zip file and installs the extension
|
||||
* As there is no information about the extension key in the zip
|
||||
* we have to use the file name to get that information
|
||||
* filename format is expected to be extensionkey_version.zip
|
||||
*
|
||||
* @param string $file Path to uploaded file
|
||||
* @param string $fileName Filename (basename) of uploaded file
|
||||
* @param bool $overwrite Overwrite existing extension if TRUE
|
||||
* @return array
|
||||
* @throws ExtensionManagerException
|
||||
*/
|
||||
protected function getExtensionFromZipFile($file, $fileName, $overwrite = false)
|
||||
{
|
||||
// Remove version and extension from filename to determine the extension key
|
||||
$extensionKey = $this->getExtensionKeyFromFileName($fileName);
|
||||
$isExtensionAvailable = $this->managementService->isAvailable($extensionKey);
|
||||
if (!$overwrite && $isExtensionAvailable) {
|
||||
throw new ExtensionManagerException('Extension is already available and overwriting is disabled.', 1342864311);
|
||||
}
|
||||
if ($isExtensionAvailable) {
|
||||
$this->copyExtensionFolderToTempFolder($extensionKey);
|
||||
}
|
||||
$this->removeFromOriginalPath = true;
|
||||
$this->fileHandlingUtility->unzipExtensionFromFile($file, $extensionKey);
|
||||
|
||||
return ['extKey' => $extensionKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes version and file extension from filename to determine extension key
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return string
|
||||
*/
|
||||
protected function getExtensionKeyFromFileName($fileName)
|
||||
{
|
||||
return preg_replace('/_(\\d+)(\\.|\\-)(\\d+)(\\.|\\-)(\\d+).*/i', '', strtolower(substr($fileName, 0, -4)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies current extension folder to typo3temp directory as backup
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
|
||||
*/
|
||||
protected function copyExtensionFolderToTempFolder($extensionKey)
|
||||
{
|
||||
$this->extensionBackupPath = Environment::getVarPath() . '/transient/' . $extensionKey . substr(sha1($extensionKey . microtime()), 0, 7) . '/';
|
||||
GeneralUtility::mkdir($this->extensionBackupPath);
|
||||
GeneralUtility::copyDirectory(
|
||||
$this->fileHandlingUtility->getExtensionDir($extensionKey),
|
||||
$this->extensionBackupPath
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the extension directory and restores the extension from the backup directory
|
||||
*
|
||||
* @param string $fileName
|
||||
* @see UploadExtensionFileController::extractAction
|
||||
*/
|
||||
protected function removeExtensionAndRestoreFromBackup($fileName)
|
||||
{
|
||||
$extDirPath = $this->fileHandlingUtility->getExtensionDir($this->getExtensionKeyFromFileName($fileName));
|
||||
if ($this->removeFromOriginalPath && is_dir($extDirPath)) {
|
||||
GeneralUtility::rmdir($extDirPath, true);
|
||||
}
|
||||
if (!empty($this->extensionBackupPath)) {
|
||||
GeneralUtility::mkdir($extDirPath);
|
||||
GeneralUtility::copyDirectory($this->extensionBackupPath, $extDirPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the backup folder in typo3temp
|
||||
*/
|
||||
protected function removeBackupFolder()
|
||||
{
|
||||
if (!empty($this->extensionBackupPath)) {
|
||||
GeneralUtility::rmdir($this->extensionBackupPath, true);
|
||||
$this->extensionBackupPath = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Domain\Model;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main extension model
|
||||
* @internal This class is a specific domain model implementation and is not part of the Public TYPO3 API.
|
||||
*/
|
||||
class Dependency extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $lowestVersion = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $highestVersion = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $dependencyTypes = [
|
||||
'depends',
|
||||
'conflicts',
|
||||
'suggests'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $specialDependencies = [
|
||||
'typo3',
|
||||
'php'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $highestVersion
|
||||
*/
|
||||
public function setHighestVersion($highestVersion)
|
||||
{
|
||||
$this->highestVersion = $highestVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHighestVersion()
|
||||
{
|
||||
return $this->highestVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $identifier
|
||||
*/
|
||||
public function setIdentifier($identifier)
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier()
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $lowestVersion
|
||||
*/
|
||||
public function setLowestVersion($lowestVersion)
|
||||
{
|
||||
$this->lowestVersion = $lowestVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLowestVersion()
|
||||
{
|
||||
return $this->lowestVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException if no valid dependency type was given
|
||||
*/
|
||||
public function setType($type)
|
||||
{
|
||||
if (in_array($type, self::$dependencyTypes)) {
|
||||
$this->type = $type;
|
||||
} else {
|
||||
throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException($type . ' was not a valid dependency type.', 1476122402);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Domain\Model;
|
||||
|
||||
/*
|
||||
* 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\Extensionmanager\Exception\ExtensionManagerException;
|
||||
|
||||
/**
|
||||
* Download Queue - storage for extensions to be downloaded
|
||||
* @internal This class is a specific domain model implementation and is not part of the Public TYPO3 API.
|
||||
*/
|
||||
class DownloadQueue implements \TYPO3\CMS\Core\SingletonInterface
|
||||
{
|
||||
/**
|
||||
* Storage for extensions to be downloaded
|
||||
*
|
||||
* @var Extension[string][string]
|
||||
*/
|
||||
protected $extensionStorage = [];
|
||||
|
||||
/**
|
||||
* Storage for extensions to be installed
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $extensionInstallStorage = [];
|
||||
|
||||
/**
|
||||
* Storage for extensions to be copied
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $extensionCopyStorage = [];
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
|
||||
*/
|
||||
protected $listUtility;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility
|
||||
*/
|
||||
public function injectListUtility(\TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility)
|
||||
{
|
||||
$this->listUtility = $listUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extension to the download queue.
|
||||
* If the extension was already requested in a different version
|
||||
* an exception is thrown.
|
||||
*
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
|
||||
* @param string $stack
|
||||
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
|
||||
*/
|
||||
public function addExtensionToQueue(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension, $stack = 'download')
|
||||
{
|
||||
if (!is_string($stack) || !in_array($stack, ['download', 'update'])) {
|
||||
throw new ExtensionManagerException('Stack has to be either "download" or "update"', 1342432103);
|
||||
}
|
||||
if (!isset($this->extensionStorage[$stack])) {
|
||||
$this->extensionStorage[$stack] = [];
|
||||
}
|
||||
if (array_key_exists($extension->getExtensionKey(), $this->extensionStorage[$stack])) {
|
||||
if ($this->extensionStorage[$stack][$extension->getExtensionKey()] !== $extension) {
|
||||
throw new ExtensionManagerException(
|
||||
$extension->getExtensionKey() . ' was requested to be downloaded in different versions (' . $extension->getVersion()
|
||||
. ' and ' . $this->extensionStorage[$stack][$extension->getExtensionKey()]->getVersion() . ').',
|
||||
1342432101
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->extensionStorage[$stack][$extension->getExtensionKey()] = $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getExtensionQueue()
|
||||
{
|
||||
return $this->extensionStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an extension from download queue
|
||||
*
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
|
||||
* @param string $stack Stack to remove extension from (download, update or install)
|
||||
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
|
||||
*/
|
||||
public function removeExtensionFromQueue(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension, $stack = 'download')
|
||||
{
|
||||
if (!is_string($stack) || !in_array($stack, ['download', 'update'])) {
|
||||
throw new ExtensionManagerException('Stack has to be either "download" or "update"', 1342432104);
|
||||
}
|
||||
if (array_key_exists($stack, $this->extensionStorage) && is_array($this->extensionStorage[$stack])) {
|
||||
if (array_key_exists($extension->getExtensionKey(), $this->extensionStorage[$stack])) {
|
||||
unset($this->extensionStorage[$stack][$extension->getExtensionKey()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extension to the install queue for later installation
|
||||
*
|
||||
* @param Extension $extension
|
||||
*/
|
||||
public function addExtensionToInstallQueue($extension)
|
||||
{
|
||||
$this->extensionInstallStorage[$extension->getExtensionKey()] = $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an extension from the install queue
|
||||
*
|
||||
* @param string $extensionKey
|
||||
*/
|
||||
public function removeExtensionFromInstallQueue($extensionKey)
|
||||
{
|
||||
if (array_key_exists($extensionKey, $this->extensionInstallStorage)) {
|
||||
unset($this->extensionInstallStorage[$extensionKey]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extension to the copy queue for later copying
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @param string $sourceFolder
|
||||
*/
|
||||
public function addExtensionToCopyQueue($extensionKey, $sourceFolder)
|
||||
{
|
||||
$this->extensionCopyStorage[$extensionKey] = $sourceFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an extension from extension copy storage
|
||||
*
|
||||
* @param $extensionKey
|
||||
*/
|
||||
public function removeExtensionFromCopyQueue($extensionKey)
|
||||
{
|
||||
if (array_key_exists($extensionKey, $this->extensionCopyStorage)) {
|
||||
unset($this->extensionCopyStorage[$extensionKey]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension installation queue
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExtensionInstallStorage()
|
||||
{
|
||||
return $this->extensionInstallStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension copy queue
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExtensionCopyStorage()
|
||||
{
|
||||
return $this->extensionCopyStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the queue contains extensions or not
|
||||
*
|
||||
* @param string $stack
|
||||
* @return bool
|
||||
*/
|
||||
public function isQueueEmpty($stack = 'download')
|
||||
{
|
||||
return empty($this->extensionStorage[$stack]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the copy queue contains extensions or not
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCopyQueueEmpty()
|
||||
{
|
||||
return empty($this->extensionCopyStorage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the install queue contains extensions or not
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInstallQueueEmpty()
|
||||
{
|
||||
return empty($this->extensionInstallStorage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the extension queue and returns old extensions
|
||||
*
|
||||
* @param string|null $stack if null, all stacks are reset
|
||||
* @return array
|
||||
*/
|
||||
public function resetExtensionQueue($stack = null)
|
||||
{
|
||||
$storage = [];
|
||||
if ($stack === null) {
|
||||
$storage = $this->extensionStorage;
|
||||
$this->extensionStorage = [];
|
||||
} elseif (isset($this->extensionStorage[$stack])) {
|
||||
$storage = $this->extensionStorage[$stack];
|
||||
$this->extensionStorage[$stack] = [];
|
||||
}
|
||||
|
||||
return $storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the copy queue and returns the old extensions
|
||||
* @return array
|
||||
*/
|
||||
public function resetExtensionCopyStorage()
|
||||
{
|
||||
$storage = $this->extensionCopyStorage;
|
||||
$this->extensionCopyStorage = [];
|
||||
|
||||
return $storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the install queue and returns the old extensions
|
||||
* @return array
|
||||
*/
|
||||
public function resetExtensionInstallStorage()
|
||||
{
|
||||
$storage = $this->extensionInstallStorage;
|
||||
$this->extensionInstallStorage = [];
|
||||
|
||||
return $storage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Domain\Model;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Main extension model
|
||||
* @internal This class is a specific domain model implementation and is not part of the Public TYPO3 API.
|
||||
*/
|
||||
class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
|
||||
{
|
||||
/**
|
||||
* Category index for distributions
|
||||
*/
|
||||
const DISTRIBUTION_CATEGORY = 10;
|
||||
|
||||
/**
|
||||
* Contains default categories.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $defaultCategories = [
|
||||
0 => 'be',
|
||||
1 => 'module',
|
||||
2 => 'fe',
|
||||
3 => 'plugin',
|
||||
4 => 'misc',
|
||||
5 => 'services',
|
||||
6 => 'templates',
|
||||
8 => 'doc',
|
||||
9 => 'example',
|
||||
self::DISTRIBUTION_CATEGORY => 'distribution'
|
||||
];
|
||||
|
||||
/**
|
||||
* Contains default states.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $defaultStates = [
|
||||
0 => 'alpha',
|
||||
1 => 'beta',
|
||||
2 => 'stable',
|
||||
3 => 'experimental',
|
||||
4 => 'test',
|
||||
5 => 'obsolete',
|
||||
6 => 'excludeFromUpdates',
|
||||
7 => 'deprecated',
|
||||
999 => 'n/a'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Object\ObjectManager
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extensionKey = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $version = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $integerVersion = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $state = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $category = 0;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $lastUpdated;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $updateComment = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $authorName = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $authorEmail = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $currentVersion = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $md5hash = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $reviewState;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $alldownloadcounter;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $serializedDependencies = '';
|
||||
|
||||
/**
|
||||
* @var \SplObjectStorage<\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency>
|
||||
*/
|
||||
protected $dependencies;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @var int
|
||||
*/
|
||||
protected $position = 0;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
|
||||
*/
|
||||
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $authorEmail
|
||||
*/
|
||||
public function setAuthorEmail($authorEmail)
|
||||
{
|
||||
$this->authorEmail = $authorEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthorEmail()
|
||||
{
|
||||
return $this->authorEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $authorName
|
||||
*/
|
||||
public function setAuthorName($authorName)
|
||||
{
|
||||
$this->authorName = $authorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthorName()
|
||||
{
|
||||
return $this->authorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $category
|
||||
*/
|
||||
public function setCategory($category)
|
||||
{
|
||||
$this->category = $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCategory()
|
||||
{
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Category String
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCategoryString()
|
||||
{
|
||||
$categoryString = '';
|
||||
if (isset(self::$defaultCategories[$this->getCategory()])) {
|
||||
$categoryString = self::$defaultCategories[$this->getCategory()];
|
||||
}
|
||||
return $categoryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns category index from a given string or an integer.
|
||||
* Fallback to 4 - 'misc' in case string is not found or integer ist out of range.
|
||||
*
|
||||
* @param string|int $category Category string or integer
|
||||
* @return int Valid category index
|
||||
*/
|
||||
public function getCategoryIndexFromStringOrNumber($category)
|
||||
{
|
||||
$categoryIndex = 4;
|
||||
if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($category)) {
|
||||
$categoryIndex = (int)$category;
|
||||
if ($categoryIndex < 0 || $categoryIndex > 10) {
|
||||
$categoryIndex = 4;
|
||||
}
|
||||
} elseif (is_string($category)) {
|
||||
$categoryIndex = array_search($category, self::$defaultCategories);
|
||||
if ($categoryIndex === false) {
|
||||
$categoryIndex = 4;
|
||||
}
|
||||
}
|
||||
return $categoryIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $description
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extensionKey
|
||||
*/
|
||||
public function setExtensionKey($extensionKey)
|
||||
{
|
||||
$this->extensionKey = $extensionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExtensionKey()
|
||||
{
|
||||
return $this->extensionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $lastUpdated
|
||||
*/
|
||||
public function setLastUpdated(\DateTime $lastUpdated)
|
||||
{
|
||||
$this->lastUpdated = $lastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getLastUpdated()
|
||||
{
|
||||
return $this->lastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $state
|
||||
*/
|
||||
public function setState($state)
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get State string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStateString()
|
||||
{
|
||||
$stateString = '';
|
||||
if (isset(self::$defaultStates[$this->getState()])) {
|
||||
$stateString = self::$defaultStates[$this->getState()];
|
||||
}
|
||||
return $stateString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either array with all default states or index/title
|
||||
* of a state entry.
|
||||
*
|
||||
* @param mixed $state state title or state index
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDefaultState($state = null)
|
||||
{
|
||||
$defaultState = '';
|
||||
if ($state === null) {
|
||||
$defaultState = self::$defaultStates;
|
||||
} else {
|
||||
if (is_string($state)) {
|
||||
$stateIndex = array_search(strtolower($state), self::$defaultStates);
|
||||
if ($stateIndex === false) {
|
||||
// default state
|
||||
$stateIndex = 999;
|
||||
}
|
||||
$defaultState = $stateIndex;
|
||||
} else {
|
||||
if (is_int($state) && $state >= 0) {
|
||||
if (array_key_exists($state, self::$defaultStates)) {
|
||||
$stateTitle = self::$defaultStates[$state];
|
||||
} else {
|
||||
// default state
|
||||
$stateTitle = 'n/a';
|
||||
}
|
||||
$defaultState = $stateTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $defaultState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $updateComment
|
||||
*/
|
||||
public function setUpdateComment($updateComment)
|
||||
{
|
||||
$this->updateComment = $updateComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUpdateComment()
|
||||
{
|
||||
return $this->updateComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $version
|
||||
*/
|
||||
public function setVersion($version)
|
||||
{
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $currentVersion
|
||||
*/
|
||||
public function setCurrentVersion($currentVersion)
|
||||
{
|
||||
$this->currentVersion = $currentVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCurrentVersion()
|
||||
{
|
||||
return $this->currentVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $md5hash
|
||||
*/
|
||||
public function setMd5hash($md5hash)
|
||||
{
|
||||
$this->md5hash = $md5hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMd5hash()
|
||||
{
|
||||
return $this->md5hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible install paths
|
||||
*
|
||||
* @static
|
||||
* @return array
|
||||
*/
|
||||
public static function returnInstallPaths()
|
||||
{
|
||||
$installPaths = [
|
||||
'System' => Environment::getFrameworkBasePath() . '/',
|
||||
'Global' => Environment::getBackendPath() . '/ext/',
|
||||
'Local' => Environment::getExtensionsPath() . '/'
|
||||
];
|
||||
return $installPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed install paths
|
||||
*
|
||||
* @static
|
||||
* @return array
|
||||
*/
|
||||
public static function returnAllowedInstallPaths()
|
||||
{
|
||||
$installPaths = self::returnInstallPaths();
|
||||
if (empty($GLOBALS['TYPO3_CONF_VARS']['EXT']['allowGlobalInstall'])) {
|
||||
unset($installPaths['Global']);
|
||||
}
|
||||
if (empty($GLOBALS['TYPO3_CONF_VARS']['EXT']['allowLocalInstall'])) {
|
||||
unset($installPaths['Local']);
|
||||
}
|
||||
return $installPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed install names: System, Global, Local
|
||||
*
|
||||
* @static
|
||||
* @return array
|
||||
*/
|
||||
public static function returnAllowedInstallTypes()
|
||||
{
|
||||
$installPaths = self::returnAllowedInstallPaths();
|
||||
return array_keys($installPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dependencies
|
||||
*/
|
||||
public function setSerializedDependencies($dependencies)
|
||||
{
|
||||
$this->serializedDependencies = $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSerializedDependencies()
|
||||
{
|
||||
return $this->serializedDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SplObjectStorage $dependencies
|
||||
*/
|
||||
public function setDependencies($dependencies)
|
||||
{
|
||||
$this->dependencies = $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \SplObjectStorage
|
||||
*/
|
||||
public function getDependencies()
|
||||
{
|
||||
if (!is_object($this->dependencies)) {
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility $extensionModelUtility */
|
||||
$extensionModelUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility::class);
|
||||
$this->setDependencies($extensionModelUtility->convertDependenciesToObjects($this->getSerializedDependencies()));
|
||||
}
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
|
||||
*/
|
||||
public function addDependency(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency)
|
||||
{
|
||||
$this->dependencies->attach($dependency);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $integerVersion
|
||||
*/
|
||||
public function setIntegerVersion($integerVersion)
|
||||
{
|
||||
$this->integerVersion = $integerVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getIntegerVersion()
|
||||
{
|
||||
return $this->integerVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $reviewState
|
||||
*/
|
||||
public function setReviewState($reviewState)
|
||||
{
|
||||
$this->reviewState = $reviewState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getReviewState()
|
||||
{
|
||||
return $this->reviewState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
*/
|
||||
public function setPosition($position)
|
||||
{
|
||||
$this->position = $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPosition()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $alldownloadcounter
|
||||
*/
|
||||
public function setAlldownloadcounter($alldownloadcounter)
|
||||
{
|
||||
$this->alldownloadcounter = $alldownloadcounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAlldownloadcounter()
|
||||
{
|
||||
return $this->alldownloadcounter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Domain\Model;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Repository mirrors object for extension manager.
|
||||
* @internal This class is a specific domain model implementation and is not part of the Public TYPO3 API.
|
||||
*/
|
||||
class Mirrors extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
|
||||
{
|
||||
/**
|
||||
* Keeps mirrors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mirrors = [];
|
||||
|
||||
/**
|
||||
* Keeps currently select mirror.
|
||||
*
|
||||
* Is array index.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $currentMirror;
|
||||
|
||||
/**
|
||||
* Keeps information if a mirror should
|
||||
* be randomly selected.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isRandomSelection = true;
|
||||
|
||||
/**
|
||||
* Method selects one specific mirror to be used.
|
||||
*
|
||||
* @param int $mirrorId number (>=1) of mirror or NULL for random selection
|
||||
* @see $currentMirror
|
||||
*/
|
||||
public function setSelect($mirrorId = null)
|
||||
{
|
||||
if ($mirrorId === null) {
|
||||
$this->isRandomSelection = true;
|
||||
} else {
|
||||
if (is_int($mirrorId) && $mirrorId >= 1 && $mirrorId <= count($this->mirrors)) {
|
||||
$this->currentMirror = $mirrorId - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns one mirror for use.
|
||||
*
|
||||
* Mirror has previously been selected or is chosen
|
||||
* randomly.
|
||||
*
|
||||
* @return array array of a mirror's properties or NULL in case of errors
|
||||
*/
|
||||
public function getMirror()
|
||||
{
|
||||
$sumMirrors = count($this->mirrors);
|
||||
if ($sumMirrors > 0) {
|
||||
if (!is_int($this->currentMirror)) {
|
||||
$this->currentMirror = rand(0, $sumMirrors - 1);
|
||||
}
|
||||
return $this->mirrors[$this->currentMirror];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mirror url from selected mirror
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMirrorUrl()
|
||||
{
|
||||
$mirror = $this->getMirror();
|
||||
$mirrorUrl = $mirror['host'] . $mirror['path'];
|
||||
return 'https://' . $mirrorUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns all available mirrors.
|
||||
*
|
||||
* @return array multidimensional array with mirrors and their properties
|
||||
* @see $mirrors, setMirrors()
|
||||
*/
|
||||
public function getMirrors()
|
||||
{
|
||||
return $this->mirrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method sets available mirrors.
|
||||
*
|
||||
* @param array $mirrors multidimensional array with mirrors and their properties
|
||||
* @see $mirrors, getMirrors()
|
||||
*/
|
||||
public function setMirrors(array $mirrors)
|
||||
{
|
||||
if (count($mirrors) >= 1) {
|
||||
$this->mirrors = $mirrors;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Domain\Model;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Repository object for extension manager.
|
||||
* @internal This class is a specific domain model implementation and is not part of the Public TYPO3 API.
|
||||
*/
|
||||
class Repository extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
|
||||
{
|
||||
/**
|
||||
* Keeps repository title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* Keeps repository description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* Keeps mirror list URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $mirrorListUrl;
|
||||
|
||||
/**
|
||||
* Keeps repository mirrors object.
|
||||
*
|
||||
* @var \TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors
|
||||
*/
|
||||
protected $mirrors;
|
||||
|
||||
/**
|
||||
* Keeps wsdl URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $wsdlUrl;
|
||||
|
||||
/**
|
||||
* Keeps last update.
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $lastUpdate;
|
||||
|
||||
/**
|
||||
* Keeps extension count.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $extensionCount;
|
||||
|
||||
/**
|
||||
* Method returns title of a repository.
|
||||
*
|
||||
* @return string title of repository
|
||||
* @see $title, setTitle()
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method sets title of a repository.
|
||||
*
|
||||
* @param string $title title of repository to set
|
||||
* @see $title, getTitle()
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
if (!empty($title) && is_string($title)) {
|
||||
$this->title = $title;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns description of a repository.
|
||||
*
|
||||
* @return string title of repository
|
||||
* @see $title, setTitle()
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method sets description of a repository.
|
||||
*
|
||||
* @param string $description title of repository to set
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
if (!empty($description) && is_string($description)) {
|
||||
$this->description = $description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns URL of a resource that contains repository mirrors.
|
||||
*
|
||||
* @return string URL of file that contains repository mirrors
|
||||
* @see $mirrorListUrl, getMirrorListUrl()
|
||||
*/
|
||||
public function getMirrorListUrl()
|
||||
{
|
||||
return $this->mirrorListUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method sets URL of a resource that contains repository mirrors.
|
||||
*
|
||||
* Parameter is typically a remote gzipped xml file.
|
||||
*
|
||||
* @param string $url URL of file that contains repository mirrors
|
||||
* @see $mirrorListUrl, getMirrorListUrl()
|
||||
*/
|
||||
public function setMirrorListUrl($url)
|
||||
{
|
||||
if (empty($url) || !empty($url) && \TYPO3\CMS\Core\Utility\GeneralUtility::isValidUrl($url)) {
|
||||
$this->mirrorListUrl = $url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns URL of repository WSDL.
|
||||
*
|
||||
* @return string URL of repository WSDL
|
||||
* @see $wsdlUrl, setWsdlUrl()
|
||||
*/
|
||||
public function getWsdlUrl()
|
||||
{
|
||||
return $this->wsdlUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method sets URL of repository WSDL.
|
||||
*
|
||||
* @param string $url URL of repository WSDL
|
||||
* @see $wsdlUrl, getWsdlUrl()
|
||||
*/
|
||||
public function setWsdlUrl($url)
|
||||
{
|
||||
if (!empty($url) && \TYPO3\CMS\Core\Utility\GeneralUtility::isValidUrl($url)) {
|
||||
$this->wsdlUrl = $url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns LastUpdate.
|
||||
*
|
||||
* @return \DateTime timestamp of last update
|
||||
*/
|
||||
public function getLastUpdate()
|
||||
{
|
||||
return $this->lastUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method sets LastUpdate.
|
||||
*
|
||||
* @param \DateTime $time URL of repository WSDL
|
||||
*/
|
||||
public function setLastUpdate(\DateTime $time)
|
||||
{
|
||||
$this->lastUpdate = $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns extension count
|
||||
*
|
||||
* @return int count of read extensions
|
||||
*/
|
||||
public function getExtensionCount()
|
||||
{
|
||||
return $this->extensionCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method sets extension count
|
||||
*
|
||||
* @param string $count count of read extensions
|
||||
*/
|
||||
public function setExtensionCount($count)
|
||||
{
|
||||
$this->extensionCount = $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method registers repository mirrors object.
|
||||
*
|
||||
* Repository mirrors object is passed by reference.
|
||||
*
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors $mirrors mirror list
|
||||
* @see $mirrors, getMirrors(), hasMirrors(), removeMirrors()
|
||||
*/
|
||||
public function addMirrors(\TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors $mirrors)
|
||||
{
|
||||
$this->mirrors = $mirrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns information if a repository mirrors
|
||||
* object has been registered to this repository.
|
||||
*
|
||||
* @return bool TRUE, if a repository mirrors object has been registered, otherwise FALSE
|
||||
* @see $mirrors, addMirrors(), getMirrors(), removeMirrors()
|
||||
*/
|
||||
public function hasMirrors()
|
||||
{
|
||||
$hasMirrors = false;
|
||||
if (is_object($this->mirrors)) {
|
||||
$hasMirrors = true;
|
||||
}
|
||||
return $hasMirrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method returns a repository mirrors object.
|
||||
*
|
||||
* @return \TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors mirrors for repository
|
||||
* @see $mirrors, addMirrors(), hasMirrors(), removeMirrors()
|
||||
*/
|
||||
public function getMirrors()
|
||||
{
|
||||
return $this->hasMirrors() ? $this->mirrors : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method unregisters a repository mirrors object.
|
||||
*
|
||||
* @see $mirrors, addMirrors(), getMirrors(), hasMirrors()
|
||||
*/
|
||||
public function removeMirrors()
|
||||
{
|
||||
unset($this->mirrors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Domain\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\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A repository for extensions
|
||||
* @internal This class is a specific domain repository implementation and is not part of the Public TYPO3 API.
|
||||
*/
|
||||
class ExtensionRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_NAME = 'tx_extensionmanager_domain_model_extension';
|
||||
|
||||
/**
|
||||
* Do not include pid in queries
|
||||
*/
|
||||
public function initializeObject()
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $defaultQuerySettings */
|
||||
$defaultQuerySettings = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface::class);
|
||||
$defaultQuerySettings->setRespectStoragePage(false);
|
||||
$this->setDefaultQuerySettings($defaultQuerySettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all extensions
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countAll()
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$query = $this->addDefaultConstraints($query);
|
||||
return $query->execute()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all extensions
|
||||
*
|
||||
* @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
|
||||
*/
|
||||
public function findAll()
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$query = $this->addDefaultConstraints($query);
|
||||
$query->setOrderings(
|
||||
[
|
||||
'lastUpdated' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
|
||||
]
|
||||
);
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an extension by extension key ordered by version
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface
|
||||
*/
|
||||
public function findByExtensionKeyOrderedByVersion($extensionKey)
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$query->matching($query->logicalAnd($query->equals('extensionKey', $extensionKey), $query->greaterThanOrEqual('reviewState', 0)));
|
||||
$query->setOrderings(['integerVersion' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING]);
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the current version by extension key
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
|
||||
*/
|
||||
public function findOneByCurrentVersionByExtensionKey($extensionKey)
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$query->matching(
|
||||
$query->logicalAnd(
|
||||
$query->equals('extensionKey', $extensionKey),
|
||||
$query->greaterThanOrEqual('reviewState', 0),
|
||||
$query->equals('currentVersion', 1)
|
||||
)
|
||||
);
|
||||
$query->setLimit(1);
|
||||
return $query->execute()->getFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find one extension by extension key and version
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @param string $version (example: 4.3.10)
|
||||
* @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
|
||||
*/
|
||||
public function findOneByExtensionKeyAndVersion($extensionKey, $version)
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
// Hint: This method must not filter out insecure extensions, if needed,
|
||||
// it should be done on a different level, or with a helper method.
|
||||
$query->matching($query->logicalAnd(
|
||||
$query->equals('extensionKey', $extensionKey),
|
||||
$query->equals('version', $version)
|
||||
));
|
||||
return $query->setLimit(1)->execute()->getFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an extension by title, author name or extension key
|
||||
* This is the function used by the TER search. It is using a
|
||||
* scoring for the matches to sort the extension with an
|
||||
* exact key match on top
|
||||
*
|
||||
* @param string $searchString The string to search for extensions
|
||||
* @return mixed
|
||||
*/
|
||||
public function findByTitleOrAuthorNameOrExtensionKey($searchString)
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable(self::TABLE_NAME);
|
||||
|
||||
$searchPlaceholderForLike = '%' . $queryBuilder->escapeLikeWildcards($searchString) . '%';
|
||||
|
||||
$searchConstraints = [
|
||||
'extension_key' => $queryBuilder->expr()->eq(
|
||||
'extension_key',
|
||||
$queryBuilder->createNamedParameter($searchString, \PDO::PARAM_STR)
|
||||
),
|
||||
'extension_key_like' => $queryBuilder->expr()->like(
|
||||
'extension_key',
|
||||
$queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
|
||||
),
|
||||
'title' => $queryBuilder->expr()->like(
|
||||
'title',
|
||||
$queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
|
||||
),
|
||||
'description' => $queryBuilder->expr()->like(
|
||||
'description',
|
||||
$queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
|
||||
),
|
||||
'author_name' => $queryBuilder->expr()->like(
|
||||
'author_name',
|
||||
$queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
|
||||
),
|
||||
];
|
||||
|
||||
$caseStatement = 'CASE ' .
|
||||
'WHEN ' . $searchConstraints['extension_key'] . ' THEN 16 ' .
|
||||
'WHEN ' . $searchConstraints['extension_key_like'] . ' THEN 8 ' .
|
||||
'WHEN ' . $searchConstraints['title'] . ' THEN 4 ' .
|
||||
'WHEN ' . $searchConstraints['description'] . ' THEN 2 ' .
|
||||
'WHEN ' . $searchConstraints['author_name'] . ' THEN 1 ' .
|
||||
'END AS ' . $queryBuilder->quoteIdentifier('position');
|
||||
|
||||
$result = $queryBuilder
|
||||
->select('*')
|
||||
->addSelectLiteral($caseStatement)
|
||||
->from(self::TABLE_NAME)
|
||||
->where(
|
||||
$queryBuilder->expr()->orX(...array_values($searchConstraints)),
|
||||
$queryBuilder->expr()->eq('current_version', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->gte('review_state', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
)
|
||||
->orderBy('position', 'DESC')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
|
||||
return $dataMapper->map(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension::class, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an extension between a certain version range ordered by version number
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @param int $lowestVersion
|
||||
* @param int $highestVersion
|
||||
* @param bool $includeCurrentVersion
|
||||
* @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface
|
||||
*/
|
||||
public function findByVersionRangeAndExtensionKeyOrderedByVersion($extensionKey, $lowestVersion = 0, $highestVersion = 0, $includeCurrentVersion = true)
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$constraint = null;
|
||||
if ($lowestVersion !== 0 && $highestVersion !== 0) {
|
||||
if ($includeCurrentVersion) {
|
||||
$constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->greaterThanOrEqual('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
|
||||
} else {
|
||||
$constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->greaterThan('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
|
||||
}
|
||||
} elseif ($lowestVersion === 0 && $highestVersion !== 0) {
|
||||
if ($includeCurrentVersion) {
|
||||
$constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->equals('extensionKey', $extensionKey));
|
||||
} else {
|
||||
$constraint = $query->logicalAnd($query->lessThan('integerVersion', $highestVersion), $query->equals('extensionKey', $extensionKey));
|
||||
}
|
||||
} elseif ($lowestVersion !== 0 && $highestVersion === 0) {
|
||||
if ($includeCurrentVersion) {
|
||||
$constraint = $query->logicalAnd($query->greaterThanOrEqual('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
|
||||
} else {
|
||||
$constraint = $query->logicalAnd($query->greaterThan('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
|
||||
}
|
||||
} elseif ($lowestVersion === 0 && $highestVersion === 0) {
|
||||
$constraint = $query->equals('extensionKey', $extensionKey);
|
||||
}
|
||||
if ($constraint) {
|
||||
$query->matching($query->logicalAnd($constraint, $query->greaterThanOrEqual('reviewState', 0)));
|
||||
}
|
||||
$query->setOrderings([
|
||||
'integerVersion' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
|
||||
]);
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all extensions with category "distribution" not published by the TYPO3 CMS Team
|
||||
*
|
||||
* @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface
|
||||
*/
|
||||
public function findAllCommunityDistributions()
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$query->matching(
|
||||
$query->logicalAnd(
|
||||
$query->equals('category', \TYPO3\CMS\Extensionmanager\Domain\Model\Extension::DISTRIBUTION_CATEGORY),
|
||||
$query->logicalNot($query->equals('ownerusername', 'typo3v4'))
|
||||
)
|
||||
);
|
||||
|
||||
$query->setOrderings([
|
||||
'alldownloadcounter' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
|
||||
]);
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all extensions with category "distribution" that are published by the TYPO3 CMS Team
|
||||
*
|
||||
* @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface
|
||||
*/
|
||||
public function findAllOfficialDistributions()
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$query->matching(
|
||||
$query->logicalAnd(
|
||||
$query->equals('category', \TYPO3\CMS\Extensionmanager\Domain\Model\Extension::DISTRIBUTION_CATEGORY),
|
||||
$query->equals('ownerusername', 'typo3v4')
|
||||
)
|
||||
);
|
||||
|
||||
$query->setOrderings([
|
||||
'alldownloadcounter' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
|
||||
]);
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count extensions with a certain key between a given version range
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @param int $lowestVersion
|
||||
* @param int $highestVersion
|
||||
* @return int
|
||||
*/
|
||||
public function countByVersionRangeAndExtensionKey($extensionKey, $lowestVersion = 0, $highestVersion = 0)
|
||||
{
|
||||
return $this->findByVersionRangeAndExtensionKeyOrderedByVersion($extensionKey, $lowestVersion, $highestVersion)->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find highest version available of an extension
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @return \TYPO3\CMS\Extensionmanager\Domain\Model\Extension
|
||||
*/
|
||||
public function findHighestAvailableVersion($extensionKey)
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$query->matching($query->logicalAnd($query->equals('extensionKey', $extensionKey), $query->greaterThanOrEqual('reviewState', 0)));
|
||||
$query->setOrderings([
|
||||
'integerVersion' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
|
||||
]);
|
||||
return $query->setLimit(1)->execute()->getFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current_version field after update.
|
||||
*
|
||||
* @param int $repositoryUid
|
||||
* @return int
|
||||
*/
|
||||
public function insertLastVersion($repositoryUid = 1)
|
||||
{
|
||||
$this->markExtensionWithMaximumVersionAsCurrent($repositoryUid);
|
||||
|
||||
return $this->getNumberOfCurrentExtensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets current_version = 1 for all extensions where the extension version is maximal.
|
||||
*
|
||||
* For performance reasons, the "native" database connection is used here directly.
|
||||
*
|
||||
* @param int $repositoryUid
|
||||
*/
|
||||
protected function markExtensionWithMaximumVersionAsCurrent($repositoryUid)
|
||||
{
|
||||
$uidsOfCurrentVersion = $this->fetchMaximalVersionsForAllExtensions($repositoryUid);
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable(self::TABLE_NAME);
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable(self::TABLE_NAME);
|
||||
$maxBindParameters = PlatformInformation::getMaxBindParameters(
|
||||
$connection->getDatabasePlatform()
|
||||
);
|
||||
|
||||
foreach (array_chunk($uidsOfCurrentVersion, $maxBindParameters - 10) as $chunk) {
|
||||
$queryBuilder
|
||||
->update(self::TABLE_NAME)
|
||||
->where(
|
||||
$queryBuilder->expr()->in(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
|
||||
)
|
||||
)
|
||||
->set('current_version', 1)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the UIDs of all maximal versions for all extensions.
|
||||
* This is done by doing a LEFT JOIN to itself ("a" and "b") and comparing
|
||||
* both integer_version fields.
|
||||
*
|
||||
* @param int $repositoryUid
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchMaximalVersionsForAllExtensions($repositoryUid)
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable(self::TABLE_NAME);
|
||||
|
||||
$queryResult = $queryBuilder
|
||||
->select('a.uid AS uid')
|
||||
->from(self::TABLE_NAME, 'a')
|
||||
->leftJoin(
|
||||
'a',
|
||||
self::TABLE_NAME,
|
||||
'b',
|
||||
$queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->eq('a.repository', $queryBuilder->quoteIdentifier('b.repository')),
|
||||
$queryBuilder->expr()->eq('a.extension_key', $queryBuilder->quoteIdentifier('b.extension_key')),
|
||||
$queryBuilder->expr()->lt('a.integer_version', $queryBuilder->quoteIdentifier('b.integer_version'))
|
||||
)
|
||||
)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'a.repository',
|
||||
$queryBuilder->createNamedParameter($repositoryUid, \PDO::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->isNull('b.extension_key')
|
||||
)
|
||||
->orderBy('a.uid')
|
||||
->execute();
|
||||
|
||||
$extensionUids = [];
|
||||
while ($row = $queryResult->fetch()) {
|
||||
$extensionUids[] = $row['uid'];
|
||||
}
|
||||
|
||||
return $extensionUids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of extensions that are current.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getNumberOfCurrentExtensions()
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable(self::TABLE_NAME);
|
||||
|
||||
return (int)$queryBuilder
|
||||
->count('*')
|
||||
->from(self::TABLE_NAME)
|
||||
->where($queryBuilder->expr()->eq(
|
||||
'current_version',
|
||||
$queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)
|
||||
))
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default constraints to the query - in this case it
|
||||
* enables us to always just search for the latest version of an extension
|
||||
*
|
||||
* @param \TYPO3\CMS\Extbase\Persistence\Generic\Query $query the query to adjust
|
||||
* @return \TYPO3\CMS\Extbase\Persistence\Generic\Query
|
||||
*/
|
||||
protected function addDefaultConstraints(\TYPO3\CMS\Extbase\Persistence\Generic\Query $query)
|
||||
{
|
||||
if ($query->getConstraint()) {
|
||||
$query->matching($query->logicalAnd(
|
||||
$query->getConstraint(),
|
||||
$query->equals('current_version', true),
|
||||
$query->greaterThanOrEqual('reviewState', 0)
|
||||
));
|
||||
} else {
|
||||
$query->matching($query->logicalAnd(
|
||||
$query->equals('current_version', true),
|
||||
$query->greaterThanOrEqual('reviewState', 0)
|
||||
));
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Domain\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* A repository for extension repositories
|
||||
* @internal This class is a specific domain repository implementation and is not part of the Public TYPO3 API.
|
||||
*/
|
||||
class RepositoryRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
|
||||
{
|
||||
/**
|
||||
* Do not include pid in queries
|
||||
*/
|
||||
public function initializeObject()
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $defaultQuerySettings */
|
||||
$defaultQuerySettings = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface::class);
|
||||
$defaultQuerySettings->setRespectStoragePage(false);
|
||||
$this->setDefaultQuerySettings($defaultQuerySettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates ExtCount and lastUpdated in Repository eg after import
|
||||
*
|
||||
* @param int $extCount
|
||||
* @param int $uid
|
||||
*/
|
||||
public function updateRepositoryCount($extCount, $uid = 1)
|
||||
{
|
||||
$repository = $this->findByUid($uid);
|
||||
|
||||
$repository->setLastUpdate(new \DateTime());
|
||||
$repository->setExtensionCount((int)$extCount);
|
||||
|
||||
$this->update($repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find main typo3.org repository
|
||||
*
|
||||
* @return \TYPO3\CMS\Extensionmanager\Domain\Model\Repository
|
||||
*/
|
||||
public function findOneTypo3OrgRepository()
|
||||
{
|
||||
$allRepositories = $this->findAll();
|
||||
$typo3OrgRepository = null;
|
||||
foreach ($allRepositories as $repository) {
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Domain\Model\Repository $repository */
|
||||
if ($repository->getTitle() === 'TYPO3.org Main Repository') {
|
||||
$typo3OrgRepository = $repository;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $typo3OrgRepository;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager;
|
||||
|
||||
/*
|
||||
* 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\Exception as CoreException;
|
||||
|
||||
/**
|
||||
* A generic Extensionmanager exception
|
||||
*/
|
||||
class Exception extends CoreException
|
||||
{
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Exception;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* An exception when dependency configuration could not be found after uploading a t3x file
|
||||
*/
|
||||
class DependencyConfigurationNotFoundException extends ExtensionManagerException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Exception;
|
||||
|
||||
/*
|
||||
* 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\Extensionmanager\Exception;
|
||||
|
||||
/**
|
||||
* An exception when something is wrong within the extension manager
|
||||
*/
|
||||
class ExtensionManagerException extends Exception
|
||||
{
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Exception;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* An exception when some dependency is unresolved
|
||||
*/
|
||||
class MissingExtensionDependencyException extends UnresolvedDependencyException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Exception;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* An exception when some dependency is unresolved
|
||||
*/
|
||||
class MissingVersionDependencyException extends UnresolvedDependencyException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Exception;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* An exception when some dependency is unresolved
|
||||
*/
|
||||
class UnresolvedDependencyException extends ExtensionManagerException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Exception;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* An exception when some dependency is unresolved
|
||||
*/
|
||||
class UnresolvedPhpDependencyException extends UnresolvedDependencyException
|
||||
{
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Exception;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* An exception when some dependency is unresolved
|
||||
*/
|
||||
class UnresolvedTypo3DependencyException extends UnresolvedDependencyException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Report;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extension status reports
|
||||
* @internal This class is a specific EXT:reports implementation and is not part of the Public TYPO3 API.
|
||||
*/
|
||||
class ExtensionStatus implements \TYPO3\CMS\Reports\StatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $ok = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $upToDate = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $error = '';
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Object\ObjectManager
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Domain\Repository\RepositoryRepository
|
||||
*/
|
||||
protected $repositoryRepository;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
|
||||
*/
|
||||
protected $listUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Localization\LanguageService
|
||||
*/
|
||||
protected $languageService;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$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->listUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\ListUtility::class);
|
||||
$this->languageService = $this->objectManager->get(\TYPO3\CMS\Core\Localization\LanguageService::class);
|
||||
$this->languageService->includeLLFile('EXT:extensionmanager/Resources/Private/Language/locallang.xlf');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines extension manager status
|
||||
*
|
||||
* @return array List of statuses
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
$status = [];
|
||||
$status['mainRepositoryStatus'] = $this->getMainRepositoryStatus();
|
||||
|
||||
$extensionStatus = $this->getSecurityStatusOfExtensions();
|
||||
$status['extensionsSecurityStatusInstalled'] = $extensionStatus->loaded ?? [];
|
||||
$status['extensionsSecurityStatusNotInstalled'] = $extensionStatus->existing ?? [];
|
||||
$status['extensionsOutdatedStatusInstalled'] = $extensionStatus->loadedoutdated ?? [];
|
||||
$status['extensionsOutdatedStatusNotInstalled'] = $extensionStatus->existingoutdated ?? [];
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check main repository status: existence, has extensions, last update younger than 7 days
|
||||
*
|
||||
* @return \TYPO3\CMS\Reports\Status
|
||||
*/
|
||||
protected function getMainRepositoryStatus()
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Domain\Model\Repository $mainRepository */
|
||||
$mainRepository = $this->repositoryRepository->findOneTypo3OrgRepository();
|
||||
|
||||
if ($mainRepository === null) {
|
||||
$value = $this->languageService->getLL('report.status.mainRepository.notFound.value');
|
||||
$message = $this->languageService->getLL('report.status.mainRepository.notFound.message');
|
||||
$severity = \TYPO3\CMS\Reports\Status::ERROR;
|
||||
} elseif ($mainRepository->getLastUpdate()->getTimestamp() < $GLOBALS['EXEC_TIME'] - 24 * 60 * 60 * 7) {
|
||||
$value = $this->languageService->getLL('report.status.mainRepository.notUpToDate.value');
|
||||
$message = $this->languageService->getLL('report.status.mainRepository.notUpToDate.message');
|
||||
$severity = \TYPO3\CMS\Reports\Status::NOTICE;
|
||||
} else {
|
||||
$value = $this->languageService->getLL('report.status.mainRepository.upToDate.value');
|
||||
$message = '';
|
||||
$severity = \TYPO3\CMS\Reports\Status::OK;
|
||||
}
|
||||
|
||||
/** @var \TYPO3\CMS\Reports\Status $status */
|
||||
$status = $this->objectManager->get(
|
||||
\TYPO3\CMS\Reports\Status::class,
|
||||
$this->languageService->getLL('report.status.mainRepository.title'),
|
||||
$value,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get security status of loaded and installed extensions
|
||||
*
|
||||
* @return \stdClass with properties 'loaded' and 'existing' containing a TYPO3\CMS\Reports\Report\Status\Status object
|
||||
*/
|
||||
protected function getSecurityStatusOfExtensions()
|
||||
{
|
||||
$extensionInformation = $this->listUtility->getAvailableAndInstalledExtensionsWithAdditionalInformation();
|
||||
$loadedInsecure = [];
|
||||
$existingInsecure = [];
|
||||
$loadedOutdated = [];
|
||||
$existingOutdated = [];
|
||||
foreach ($extensionInformation as $extensionKey => $information) {
|
||||
if (
|
||||
array_key_exists('terObject', $information)
|
||||
&& $information['terObject'] instanceof \TYPO3\CMS\Extensionmanager\Domain\Model\Extension
|
||||
) {
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $terObject */
|
||||
$terObject = $information['terObject'];
|
||||
$insecureStatus = $terObject->getReviewState();
|
||||
if ($insecureStatus === -1) {
|
||||
if (
|
||||
array_key_exists('installed', $information)
|
||||
&& $information['installed'] === true
|
||||
) {
|
||||
$loadedInsecure[] = [
|
||||
'extensionKey' => $extensionKey,
|
||||
'version' => $terObject->getVersion(),
|
||||
];
|
||||
} else {
|
||||
$existingInsecure[] = [
|
||||
'extensionKey' => $extensionKey,
|
||||
'version' => $terObject->getVersion(),
|
||||
];
|
||||
}
|
||||
} elseif ($insecureStatus === -2) {
|
||||
if (
|
||||
array_key_exists('installed', $information)
|
||||
&& $information['installed'] === true
|
||||
) {
|
||||
$loadedOutdated[] = [
|
||||
'extensionKey' => $extensionKey,
|
||||
'version' => $terObject->getVersion(),
|
||||
];
|
||||
} else {
|
||||
$existingOutdated[] = [
|
||||
'extensionKey' => $extensionKey,
|
||||
'version' => $terObject->getVersion(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result = new \stdClass();
|
||||
|
||||
if (empty($loadedInsecure)) {
|
||||
$value = $this->languageService->getLL('report.status.loadedExtensions.noInsecureExtensionLoaded.value');
|
||||
$message = '';
|
||||
$severity = \TYPO3\CMS\Reports\Status::OK;
|
||||
} else {
|
||||
$value = sprintf(
|
||||
$this->languageService->getLL('report.status.loadedExtensions.insecureExtensionLoaded.value'),
|
||||
count($loadedInsecure)
|
||||
);
|
||||
$extensionList = [];
|
||||
foreach ($loadedInsecure as $insecureExtension) {
|
||||
$extensionList[] = sprintf(
|
||||
$this->languageService->getLL('report.status.loadedExtensions.insecureExtensionLoaded.message.extension'),
|
||||
$insecureExtension['extensionKey'],
|
||||
$insecureExtension['version']
|
||||
);
|
||||
}
|
||||
$message = sprintf(
|
||||
$this->languageService->getLL('report.status.loadedExtensions.insecureExtensionLoaded.message'),
|
||||
implode('', $extensionList)
|
||||
);
|
||||
$severity = \TYPO3\CMS\Reports\Status::ERROR;
|
||||
}
|
||||
$result->loaded = $this->objectManager->get(
|
||||
\TYPO3\CMS\Reports\Status::class,
|
||||
$this->languageService->getLL('report.status.loadedExtensions.title'),
|
||||
$value,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
|
||||
if (empty($existingInsecure)) {
|
||||
$value = $this->languageService->getLL('report.status.existingExtensions.noInsecureExtensionExists.value');
|
||||
$message = '';
|
||||
$severity = \TYPO3\CMS\Reports\Status::OK;
|
||||
} else {
|
||||
$value = sprintf(
|
||||
$this->languageService->getLL('report.status.existingExtensions.insecureExtensionExists.value'),
|
||||
count($existingInsecure)
|
||||
);
|
||||
$extensionList = [];
|
||||
foreach ($existingInsecure as $insecureExtension) {
|
||||
$extensionList[] = sprintf(
|
||||
$this->languageService->getLL('report.status.existingExtensions.insecureExtensionExists.message.extension'),
|
||||
$insecureExtension['extensionKey'],
|
||||
$insecureExtension['version']
|
||||
);
|
||||
}
|
||||
$message = sprintf(
|
||||
$this->languageService->getLL('report.status.existingExtensions.insecureExtensionExists.message'),
|
||||
implode('', $extensionList)
|
||||
);
|
||||
$severity = \TYPO3\CMS\Reports\Status::WARNING;
|
||||
}
|
||||
$result->existing = $this->objectManager->get(
|
||||
\TYPO3\CMS\Reports\Status::class,
|
||||
$this->languageService->getLL('report.status.existingExtensions.title'),
|
||||
$value,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
|
||||
if (empty($loadedOutdated)) {
|
||||
$value = $this->languageService->getLL('report.status.loadedOutdatedExtensions.noOutdatedExtensionLoaded.value');
|
||||
$message = '';
|
||||
$severity = \TYPO3\CMS\Reports\Status::OK;
|
||||
} else {
|
||||
$value = sprintf(
|
||||
$this->languageService->getLL('report.status.loadedOutdatedExtensions.outdatedExtensionLoaded.value'),
|
||||
count($loadedOutdated)
|
||||
);
|
||||
$extensionList = [];
|
||||
foreach ($loadedOutdated as $outdatedExtension) {
|
||||
$extensionList[] = sprintf(
|
||||
$this->languageService->getLL('report.status.loadedOutdatedExtensions.outdatedExtensionLoaded.message.extension'),
|
||||
$outdatedExtension['extensionKey'],
|
||||
$outdatedExtension['version']
|
||||
);
|
||||
}
|
||||
$message = sprintf(
|
||||
$this->languageService->getLL('report.status.loadedOutdatedExtensions.outdatedExtensionLoaded.message'),
|
||||
implode('', $extensionList)
|
||||
);
|
||||
$severity = \TYPO3\CMS\Reports\Status::WARNING;
|
||||
}
|
||||
$result->loadedoutdated = $this->objectManager->get(
|
||||
\TYPO3\CMS\Reports\Status::class,
|
||||
$this->languageService->getLL('report.status.loadedOutdatedExtensions.title'),
|
||||
$value,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
|
||||
if (empty($existingOutdated)) {
|
||||
$value = $this->languageService->getLL('report.status.existingOutdatedExtensions.noOutdatedExtensionExists.value');
|
||||
$message = '';
|
||||
$severity = \TYPO3\CMS\Reports\Status::OK;
|
||||
} else {
|
||||
$value = sprintf(
|
||||
$this->languageService->getLL('report.status.existingOutdatedExtensions.outdatedExtensionExists.value'),
|
||||
count($existingOutdated)
|
||||
);
|
||||
$extensionList = [];
|
||||
foreach ($existingOutdated as $outdatedExtension) {
|
||||
$extensionList[] = sprintf(
|
||||
$this->languageService->getLL('report.status.existingOutdatedExtensions.outdatedExtensionExists.message.extension'),
|
||||
$outdatedExtension['extensionKey'],
|
||||
$outdatedExtension['version']
|
||||
);
|
||||
}
|
||||
$message = sprintf(
|
||||
$this->languageService->getLL('report.status.existingOutdatedExtensions.outdatedExtensionExists.message'),
|
||||
implode('', $extensionList)
|
||||
);
|
||||
$severity = \TYPO3\CMS\Reports\Status::WARNING;
|
||||
}
|
||||
$result->existingoutdated = $this->objectManager->get(
|
||||
\TYPO3\CMS\Reports\Status::class,
|
||||
$this->languageService->getLL('report.status.existingOutdatedExtensions.title'),
|
||||
$value,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Service;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
|
||||
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
|
||||
|
||||
/**
|
||||
* Service class for managing multiple step processes (dependencies for example)
|
||||
*/
|
||||
class ExtensionManagementService implements \TYPO3\CMS\Core\SingletonInterface
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue
|
||||
*/
|
||||
protected $downloadQueue;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility
|
||||
*/
|
||||
protected $dependencyUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility
|
||||
*/
|
||||
protected $installUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility
|
||||
*/
|
||||
protected $extensionModelUtility;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extensionmanager\Utility\DownloadUtility
|
||||
*/
|
||||
protected $downloadUtility;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $automaticInstallationEnabled = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $skipDependencyCheck = false;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue $downloadQueue
|
||||
*/
|
||||
public function injectDownloadQueue(\TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue $downloadQueue)
|
||||
{
|
||||
$this->downloadQueue = $downloadQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility
|
||||
*/
|
||||
public function injectDependencyUtility(\TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility)
|
||||
{
|
||||
$this->dependencyUtility = $dependencyUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility
|
||||
*/
|
||||
public function injectInstallUtility(\TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility)
|
||||
{
|
||||
$this->installUtility = $installUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility $extensionModelUtility
|
||||
*/
|
||||
public function injectExtensionModelUtility(\TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility $extensionModelUtility)
|
||||
{
|
||||
$this->extensionModelUtility = $extensionModelUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extensionmanager\Utility\DownloadUtility $downloadUtility
|
||||
*/
|
||||
public function injectDownloadUtility(\TYPO3\CMS\Extensionmanager\Utility\DownloadUtility $downloadUtility)
|
||||
{
|
||||
$this->downloadUtility = $downloadUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extensionKey
|
||||
*/
|
||||
public function markExtensionForInstallation($extensionKey)
|
||||
{
|
||||
// We have to check for dependencies of the extension first, before marking it for installation
|
||||
// because this extension might have dependencies, which need to be installed first
|
||||
$this->installUtility->reloadAvailableExtensions();
|
||||
$extension = $this->getExtension($extensionKey);
|
||||
$this->dependencyUtility->checkDependencies($extension);
|
||||
$this->downloadQueue->addExtensionToInstallQueue($extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an extension for copy
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @param string $sourceFolder
|
||||
*/
|
||||
public function markExtensionForCopy($extensionKey, $sourceFolder)
|
||||
{
|
||||
$this->downloadQueue->addExtensionToCopyQueue($extensionKey, $sourceFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an extension for download
|
||||
*
|
||||
* @param Extension $extension
|
||||
*/
|
||||
public function markExtensionForDownload(Extension $extension)
|
||||
{
|
||||
// We have to check for dependencies of the extension first, before marking it for download
|
||||
// because this extension might have dependencies, which need to be downloaded and installed first
|
||||
$this->dependencyUtility->checkDependencies($extension);
|
||||
if (!$this->dependencyUtility->hasDependencyErrors()) {
|
||||
$this->downloadQueue->addExtensionToQueue($extension);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Extension $extension
|
||||
*/
|
||||
public function markExtensionForUpdate(Extension $extension)
|
||||
{
|
||||
// We have to check for dependencies of the extension first, before marking it for download
|
||||
// because this extension might have dependencies, which need to be downloaded and installed first
|
||||
$this->dependencyUtility->checkDependencies($extension);
|
||||
$this->downloadQueue->addExtensionToQueue($extension, 'update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the dependency check for system environment (PHP, TYPO3) before extension installation
|
||||
*
|
||||
* @param bool $skipDependencyCheck
|
||||
*/
|
||||
public function setSkipDependencyCheck($skipDependencyCheck)
|
||||
{
|
||||
$this->skipDependencyCheck = $skipDependencyCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $automaticInstallationEnabled
|
||||
*/
|
||||
public function setAutomaticInstallationEnabled($automaticInstallationEnabled)
|
||||
{
|
||||
$this->automaticInstallationEnabled = (bool)$automaticInstallationEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the extension
|
||||
*
|
||||
* @param Extension $extension
|
||||
* @return bool|array Returns FALSE if dependencies cannot be resolved, otherwise array with installation information
|
||||
*/
|
||||
public function installExtension(Extension $extension)
|
||||
{
|
||||
$this->downloadExtension($extension);
|
||||
if (!$this->checkDependencies($extension)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$downloadedDependencies = [];
|
||||
$updatedDependencies = [];
|
||||
$installQueue = [];
|
||||
|
||||
// First resolve all dependencies and the sub-dependencies until all queues are empty as new extensions might be
|
||||
// added each time
|
||||
// Extensions have to be installed in reverse order. Extensions which were added at last are dependencies of
|
||||
// earlier ones and need to be available before
|
||||
while (!$this->downloadQueue->isCopyQueueEmpty()
|
||||
|| !$this->downloadQueue->isQueueEmpty('download')
|
||||
|| !$this->downloadQueue->isQueueEmpty('update')
|
||||
) {
|
||||
// First copy all available extension
|
||||
// This might change other queues again
|
||||
$copyQueue = $this->downloadQueue->resetExtensionCopyStorage();
|
||||
if (!empty($copyQueue)) {
|
||||
$this->copyDependencies($copyQueue);
|
||||
}
|
||||
$installQueue = array_merge($this->downloadQueue->resetExtensionInstallStorage(), $installQueue);
|
||||
// Get download and update information
|
||||
$queue = $this->downloadQueue->resetExtensionQueue();
|
||||
if (!empty($queue['download'])) {
|
||||
$downloadedDependencies = array_merge($downloadedDependencies, $this->downloadDependencies($queue['download']));
|
||||
}
|
||||
$installQueue = array_merge($this->downloadQueue->resetExtensionInstallStorage(), $installQueue);
|
||||
if ($this->automaticInstallationEnabled) {
|
||||
if (!empty($queue['update'])) {
|
||||
$this->downloadDependencies($queue['update']);
|
||||
$updatedDependencies = array_merge($updatedDependencies, $this->uninstallDependenciesToBeUpdated($queue['update']));
|
||||
}
|
||||
$installQueue = array_merge($this->downloadQueue->resetExtensionInstallStorage(), $installQueue);
|
||||
}
|
||||
}
|
||||
|
||||
// If there were any dependency errors we have to abort here
|
||||
if ($this->dependencyUtility->hasDependencyErrors()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attach extension to install queue
|
||||
$this->downloadQueue->addExtensionToInstallQueue($extension);
|
||||
$installQueue += $this->downloadQueue->resetExtensionInstallStorage();
|
||||
$installedDependencies = [];
|
||||
if ($this->automaticInstallationEnabled) {
|
||||
$installedDependencies = $this->installDependencies($installQueue);
|
||||
}
|
||||
|
||||
return array_merge($downloadedDependencies, $updatedDependencies, $installedDependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unresolved dependency errors
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencyErrors()
|
||||
{
|
||||
return $this->dependencyUtility->getDependencyErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extensionKey
|
||||
* @return Extension
|
||||
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
|
||||
*/
|
||||
public function getExtension($extensionKey)
|
||||
{
|
||||
return $this->extensionModelUtility->mapExtensionArrayToModel(
|
||||
$this->installUtility->enrichExtensionWithDetails($extensionKey)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an extension is available in the system
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @return bool
|
||||
*/
|
||||
public function isAvailable($extensionKey)
|
||||
{
|
||||
return $this->installUtility->isAvailable($extensionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
$this->installUtility->reloadPackageInformation($extensionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an extension
|
||||
*
|
||||
* @param Extension $extension
|
||||
*/
|
||||
protected function downloadExtension(Extension $extension)
|
||||
{
|
||||
$this->downloadMainExtension($extension);
|
||||
$this->setInExtensionRepository($extension->getExtensionKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check dependencies for an extension and its required extensions
|
||||
*
|
||||
* @param Extension $extension
|
||||
* @return bool Returns TRUE if all dependencies can be resolved, otherwise FALSE
|
||||
*/
|
||||
protected function checkDependencies(Extension $extension)
|
||||
{
|
||||
$this->dependencyUtility->setSkipDependencyCheck($this->skipDependencyCheck);
|
||||
$this->dependencyUtility->checkDependencies($extension);
|
||||
|
||||
return !$this->dependencyUtility->hasDependencyErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the repository in an extension
|
||||
* (Initialisation/Extensions) depending on the extension
|
||||
* that is currently installed
|
||||
*
|
||||
* @param string $extensionKey
|
||||
*/
|
||||
protected function setInExtensionRepository($extensionKey)
|
||||
{
|
||||
$paths = Extension::returnInstallPaths();
|
||||
$path = $paths[$this->downloadUtility->getDownloadPath()];
|
||||
$localExtensionStorage = $path . $extensionKey . '/Initialisation/Extensions/';
|
||||
$this->dependencyUtility->setLocalExtensionStorage($localExtensionStorage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies locally provided extensions to typo3conf/ext
|
||||
*
|
||||
* @param array $copyQueue
|
||||
*/
|
||||
protected function copyDependencies(array $copyQueue)
|
||||
{
|
||||
$installPaths = Extension::returnAllowedInstallPaths();
|
||||
foreach ($copyQueue as $extensionKey => $sourceFolder) {
|
||||
$destination = $installPaths['Local'] . $extensionKey;
|
||||
GeneralUtility::mkdir($destination);
|
||||
GeneralUtility::copyDirectory($sourceFolder . $extensionKey, $destination);
|
||||
$this->markExtensionForInstallation($extensionKey);
|
||||
$this->downloadQueue->removeExtensionFromCopyQueue($extensionKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall extensions that will be updated
|
||||
* This is not strictly necessary but cleaner all in all
|
||||
*
|
||||
* @param Extension[] $updateQueue
|
||||
* @return array
|
||||
*/
|
||||
protected function uninstallDependenciesToBeUpdated(array $updateQueue)
|
||||
{
|
||||
$resolvedDependencies = [];
|
||||
foreach ($updateQueue as $extensionToUpdate) {
|
||||
$this->installUtility->uninstall($extensionToUpdate->getExtensionKey());
|
||||
$resolvedDependencies['updated'][$extensionToUpdate->getExtensionKey()] = $extensionToUpdate;
|
||||
}
|
||||
return $resolvedDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install dependent extensions
|
||||
*
|
||||
* @param array $installQueue
|
||||
* @return array
|
||||
*/
|
||||
protected function installDependencies(array $installQueue)
|
||||
{
|
||||
if (empty($installQueue)) {
|
||||
return [];
|
||||
}
|
||||
$this->emitWillInstallExtensionsSignal($installQueue);
|
||||
$resolvedDependencies = [];
|
||||
$this->installUtility->install(...array_keys($installQueue));
|
||||
foreach ($installQueue as $extensionKey => $_) {
|
||||
$this->emitHasInstalledExtensionSignal($extensionKey);
|
||||
if (!isset($resolvedDependencies['installed']) || !is_array($resolvedDependencies['installed'])) {
|
||||
$resolvedDependencies['installed'] = [];
|
||||
}
|
||||
$resolvedDependencies['installed'][$extensionKey] = $extensionKey;
|
||||
}
|
||||
return $resolvedDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download dependencies
|
||||
* expects an array of extension objects to download
|
||||
*
|
||||
* @param Extension[] $downloadQueue
|
||||
* @return array
|
||||
*/
|
||||
protected function downloadDependencies(array $downloadQueue)
|
||||
{
|
||||
$resolvedDependencies = [];
|
||||
foreach ($downloadQueue as $extensionToDownload) {
|
||||
$this->downloadUtility->download($extensionToDownload);
|
||||
$this->downloadQueue->removeExtensionFromQueue($extensionToDownload);
|
||||
$resolvedDependencies['downloaded'][$extensionToDownload->getExtensionKey()] = $extensionToDownload;
|
||||
$this->markExtensionForInstallation($extensionToDownload->getExtensionKey());
|
||||
}
|
||||
return $resolvedDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and resolve dependencies
|
||||
*
|
||||
* @param Extension $extension
|
||||
* @return array
|
||||
*/
|
||||
public function getAndResolveDependencies(Extension $extension)
|
||||
{
|
||||
$this->dependencyUtility->setSkipDependencyCheck($this->skipDependencyCheck);
|
||||
$this->dependencyUtility->checkDependencies($extension);
|
||||
$installQueue = $this->downloadQueue->getExtensionInstallStorage();
|
||||
if (is_array($installQueue) && !empty($installQueue)) {
|
||||
$installQueue = ['install' => $installQueue];
|
||||
}
|
||||
return array_merge($this->downloadQueue->getExtensionQueue(), $installQueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the extension the user wants to install
|
||||
* This is separated from downloading the dependencies
|
||||
* as an extension is able to provide it's own dependencies
|
||||
*
|
||||
* @param Extension $extension
|
||||
*/
|
||||
public function downloadMainExtension(Extension $extension)
|
||||
{
|
||||
// The extension object has a uid if the extension is not present in the system
|
||||
// or an update of a present extension is triggered.
|
||||
if ($extension->getUid()) {
|
||||
$this->downloadUtility->download($extension);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $installQueue
|
||||
*/
|
||||
protected function emitWillInstallExtensionsSignal(array $installQueue)
|
||||
{
|
||||
$this->getSignalSlotDispatcher()->dispatch(__CLASS__, 'willInstallExtensions', [$installQueue]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extensionKey
|
||||
* @deprecated since TYPO3 v9, will be removed with TYPO3v10
|
||||
*/
|
||||
protected function emitHasInstalledExtensionSignal($extensionKey)
|
||||
{
|
||||
$this->getSignalSlotDispatcher()->dispatch(__CLASS__, 'hasInstalledExtensions', [$extensionKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SignalSlot dispatcher
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
protected function getSignalSlotDispatcher()
|
||||
{
|
||||
if (!isset($this->signalSlotDispatcher)) {
|
||||
$this->signalSlotDispatcher = GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(Dispatcher::class);
|
||||
}
|
||||
return $this->signalSlotDispatcher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\Task;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update extension list from TER task
|
||||
* @internal This class is a specific EXT:scheduler task implementation and is not part of the Public TYPO3 API.
|
||||
*/
|
||||
class UpdateExtensionListTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask
|
||||
{
|
||||
/**
|
||||
* Public method, called by scheduler.
|
||||
*
|
||||
* @return bool TRUE on success
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// Throws exceptions if something went wrong
|
||||
$this->updateExtensionList();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update extension list
|
||||
*
|
||||
* @TODO: Adapt to multiple repositories if the Helper can handle this
|
||||
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
|
||||
*/
|
||||
protected function updateExtensionList()
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
|
||||
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
|
||||
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Utility\Repository\Helper $repositoryHelper */
|
||||
$repositoryHelper = $objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\Repository\Helper::class);
|
||||
$repositoryHelper->updateExtList();
|
||||
|
||||
/** @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager $persistenceManager */
|
||||
$persistenceManager = $objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class);
|
||||
$persistenceManager->persistAll();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers\Be;
|
||||
|
||||
/*
|
||||
* 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\Extensionmanager\Controller\AbstractController;
|
||||
|
||||
/**
|
||||
* Special ViewHelper for the BE module of the Extension Manager. Loads JS code for triggering
|
||||
* refresh events.
|
||||
*
|
||||
* = Examples =
|
||||
*
|
||||
* <code title="Simple">
|
||||
* <em:be.container triggers="{triggers}" />
|
||||
* </code>
|
||||
* <output>
|
||||
* Writes some JS inline code
|
||||
* </output>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TriggerViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Be\AbstractBackendViewHelper
|
||||
{
|
||||
/**
|
||||
* Initializes the arguments
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('triggers', 'array', 'Defined triggers to be forwarded to client (e.g. refreshing backend widgets)', false, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads some JS inline code based on a list of triggers. This is used to reload the main
|
||||
* menu when modules are loaded/unloaded.
|
||||
*
|
||||
* @return string This ViewHelper does not return any content
|
||||
* @see \TYPO3\CMS\Backend\Template\DocumentTemplate
|
||||
* @see \TYPO3\CMS\Core\Page\PageRenderer
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$pageRenderer = $this->getPageRenderer();
|
||||
// Handle triggers
|
||||
if (!empty($this->arguments['triggers'][AbstractController::TRIGGER_RefreshModuleMenu])) {
|
||||
$pageRenderer->addJsInlineCode(
|
||||
AbstractController::TRIGGER_RefreshModuleMenu,
|
||||
'if (top && top.TYPO3.ModuleMenu.App) { top.TYPO3.ModuleMenu.App.refreshMenu(); }'
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($this->arguments['triggers'][AbstractController::TRIGGER_RefreshTopbar])) {
|
||||
$pageRenderer->addJsInlineCode(
|
||||
AbstractController::TRIGGER_RefreshTopbar,
|
||||
'if (top && top.TYPO3.Backend && top.TYPO3.Backend.Topbar) { top.TYPO3.Backend.Topbar.refresh(); }'
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
|
||||
|
||||
/**
|
||||
* Renders the distribution image
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class DistributionImageViewHelper extends AbstractTagBasedViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'img';
|
||||
|
||||
/**
|
||||
* Initialize arguments.
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('extensionkey', 'string', '', true);
|
||||
$this->registerArgument('width', 'int', 'width of the image');
|
||||
$this->registerArgument('height', 'int', 'height of the image');
|
||||
$this->registerUniversalTagAttributes();
|
||||
$this->registerTagAttribute('alt', 'string', 'Specifies an alternate text for an image', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the destribution preview image for the given extension
|
||||
*
|
||||
* @return string rendered tag.
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$extensionKey = $this->arguments['extensionkey'];
|
||||
$width = $this->arguments['width'];
|
||||
$height = $this->arguments['height'];
|
||||
$content = '';
|
||||
|
||||
$src = $this->findImage($extensionKey);
|
||||
$uri = $this->getImageUri($src);
|
||||
|
||||
if (!empty($uri)) {
|
||||
if ($width) {
|
||||
$this->tag->addAttribute('width', (int)$width);
|
||||
}
|
||||
if ($height) {
|
||||
$this->tag->addAttribute('height', (int)$height);
|
||||
}
|
||||
$this->tag->addAttribute('src', $uri);
|
||||
$content = $this->tag->render();
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the distrubution image
|
||||
*
|
||||
* @param string $extensionKey
|
||||
* @return string
|
||||
*/
|
||||
protected function findImage($extensionKey)
|
||||
{
|
||||
$paths = [];
|
||||
$paths[] = 'EXT:' . $extensionKey . '/Resources/Public/Images/Distribution.svg';
|
||||
$paths[] = 'EXT:' . $extensionKey . '/Resources/Public/Images/Distribution.png';
|
||||
$paths[] = 'EXT:extensionmanager/Resources/Public/Images/Distribution.svg';
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$absFileName = GeneralUtility::getFileAbsFileName($path);
|
||||
if (file_exists($absFileName)) {
|
||||
return $absFileName;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image uri
|
||||
*
|
||||
* @param string $src
|
||||
* @return string
|
||||
*/
|
||||
protected function getImageUri($src)
|
||||
{
|
||||
$uri = GeneralUtility::getFileAbsFileName($src);
|
||||
if ($uri !== '' && !file_exists($uri)) {
|
||||
$uri = '';
|
||||
}
|
||||
if ($uri !== '') {
|
||||
$uri = '../' . PathUtility::stripPathSitePrefix($uri);
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* 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\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
|
||||
|
||||
/**
|
||||
* ViewHelper
|
||||
* @internal
|
||||
*/
|
||||
class DownloadExtensionViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'form';
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Service\ExtensionService
|
||||
*/
|
||||
protected $extensionService;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService
|
||||
*/
|
||||
public function injectExtensionService(\TYPO3\CMS\Extbase\Service\ExtensionService $extensionService)
|
||||
{
|
||||
$this->extensionService = $extensionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize arguments.
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('extension', Extension::class, '', true);
|
||||
$this->registerTagAttribute('enctype', 'string', 'MIME type with which the form is submitted');
|
||||
$this->registerTagAttribute('method', 'string', 'Transfer type (GET or POST)');
|
||||
$this->registerTagAttribute('name', 'string', 'Name of form');
|
||||
$this->registerTagAttribute('onreset', 'string', 'JavaScript: On reset of the form');
|
||||
$this->registerTagAttribute('onsubmit', 'string', 'JavaScript: On submit of the form');
|
||||
$this->registerUniversalTagAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a download link
|
||||
*
|
||||
* @return string the rendered a tag
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
/** @var Extension $extension */
|
||||
$extension = $this->arguments['extension'];
|
||||
$installPaths = Extension::returnAllowedInstallPaths();
|
||||
if (empty($installPaths)) {
|
||||
return '';
|
||||
}
|
||||
$pathSelector = '<ul class="is-hidden">';
|
||||
foreach ($installPaths as $installPathType => $installPath) {
|
||||
$pathSelector .= '<li>
|
||||
<input type="radio" id="' . htmlspecialchars($extension->getExtensionKey()) . '-downloadPath-' . htmlspecialchars($installPathType) . '" name="' . htmlspecialchars($this->getFieldNamePrefix()) . '[downloadPath]" class="downloadPath" value="' . htmlspecialchars($installPathType) . '" ' . ($installPathType === 'Local' ? 'checked="checked"' : '') . ' />
|
||||
<label for="' . htmlspecialchars($extension->getExtensionKey()) . '-downloadPath-' . htmlspecialchars($installPathType) . '">' . htmlspecialchars($installPathType) . '</label>
|
||||
</li>';
|
||||
}
|
||||
$pathSelector .= '</ul>';
|
||||
$uriBuilder = $this->renderingContext->getControllerContext()->getUriBuilder();
|
||||
$action = 'checkDependencies';
|
||||
$uriBuilder->reset();
|
||||
$uriBuilder->setFormat('json');
|
||||
$uri = $uriBuilder->uriFor($action, [
|
||||
'extension' => (int)$extension->getUid()
|
||||
], 'Download');
|
||||
$this->tag->addAttribute('data-href', $uri);
|
||||
|
||||
$automaticInstallation = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
|
||||
$labelKeySuffix = $automaticInstallation ? '' : '.downloadOnly';
|
||||
$label = '
|
||||
<div class="btn-group">
|
||||
<button
|
||||
title="' . LocalizationUtility::translate('extensionList.downloadViewHelper.submit' . $labelKeySuffix, 'extensionmanager') . '"
|
||||
type="submit"
|
||||
class="btn btn-default"
|
||||
value="' . LocalizationUtility::translate('extensionList.downloadViewHelper.submit' . $labelKeySuffix, 'extensionmanager') . '"
|
||||
>
|
||||
<span class="t3-icon fa fa-cloud-download"></span>
|
||||
</button>
|
||||
</div>';
|
||||
|
||||
$this->tag->setContent($label . $pathSelector);
|
||||
$this->tag->addAttribute('class', 'download');
|
||||
return '<div id="' . htmlspecialchars($extension->getExtensionKey()) . '-downloadFromTer" class="downloadFromTer">' . $this->tag->render() . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field name prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFieldNamePrefix()
|
||||
{
|
||||
if ($this->hasArgument('fieldNamePrefix')) {
|
||||
return $this->arguments['fieldNamePrefix'];
|
||||
}
|
||||
return $this->getDefaultFieldNamePrefix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the default field name prefix for this form
|
||||
*
|
||||
* @return string default field name prefix
|
||||
*/
|
||||
protected function getDefaultFieldNamePrefix()
|
||||
{
|
||||
$request = $this->renderingContext->getControllerContext()->getRequest();
|
||||
if ($this->hasArgument('extensionName')) {
|
||||
$extensionName = $this->arguments['extensionName'];
|
||||
} else {
|
||||
$extensionName = $request->getControllerExtensionName();
|
||||
}
|
||||
if ($this->hasArgument('pluginName')) {
|
||||
$pluginName = $this->arguments['pluginName'];
|
||||
} else {
|
||||
$pluginName = $request->getPluginName();
|
||||
}
|
||||
if ($extensionName !== null && $pluginName != null) {
|
||||
return $this->extensionService->getPluginNamespace($extensionName, $pluginName);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* 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 TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
|
||||
|
||||
/**
|
||||
* Returns a string meant to be used as css class stating whether an extension is
|
||||
* available or installed
|
||||
* @internal
|
||||
*/
|
||||
class InstallationStateCssClassViewHelper extends AbstractViewHelper
|
||||
{
|
||||
use CompileWithRenderStatic;
|
||||
|
||||
/**
|
||||
* Initialize arguments
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
$this->registerArgument('needle', 'string', '', true);
|
||||
$this->registerArgument('haystack', 'array', '', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string meant to be used as css class
|
||||
* 'installed' => if an extension is installed
|
||||
* 'available' => if an extension is available in the system
|
||||
* '' (empty string) => if neither installed nor available
|
||||
*
|
||||
* @param array $arguments
|
||||
* @param \Closure $renderChildrenClosure
|
||||
* @param RenderingContextInterface $renderingContext
|
||||
*
|
||||
* @return string the rendered a tag
|
||||
*/
|
||||
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
|
||||
{
|
||||
$needle = $arguments['needle'];
|
||||
$haystack = $arguments['haystack'];
|
||||
if (array_key_exists($needle, $haystack)) {
|
||||
if (isset($haystack[$needle]['installed']) && $haystack[$needle]['installed'] === true) {
|
||||
return 'installed';
|
||||
}
|
||||
return 'available';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* 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 TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
|
||||
|
||||
/**
|
||||
* ViewHelper to let 3rd-party extensions process the list of available
|
||||
* actions for a given extension.
|
||||
* @internal
|
||||
*/
|
||||
class ProcessAvailableActionsViewHelper extends AbstractTagBasedViewHelper
|
||||
{
|
||||
const SIGNAL_ProcessActions = 'processActions';
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
|
||||
*/
|
||||
protected $signalSlotDispatcher;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
|
||||
*/
|
||||
public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
|
||||
{
|
||||
$this->signalSlotDispatcher = $signalSlotDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize arguments
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('extension', 'string', '', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the list of actions.
|
||||
*
|
||||
* @return string the rendered list of actions
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$extension = $this->arguments['extension'];
|
||||
$html = $this->renderChildren();
|
||||
$actions = preg_split('#\\n\\s*#s', trim($html));
|
||||
|
||||
$actions = $this->emitProcessActionsSignal($extension, $actions);
|
||||
|
||||
return implode(' ', $actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a signal after the list of actions is processed
|
||||
*
|
||||
* @param string $extension
|
||||
* @param array $actions
|
||||
* @return array Modified action array
|
||||
*/
|
||||
protected function emitProcessActionsSignal($extension, array $actions)
|
||||
{
|
||||
$this->signalSlotDispatcher->dispatch(
|
||||
__CLASS__,
|
||||
static::SIGNAL_ProcessActions,
|
||||
[
|
||||
$extension,
|
||||
&$actions,
|
||||
]
|
||||
);
|
||||
return $actions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* 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\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\Link\ActionViewHelper;
|
||||
|
||||
/**
|
||||
* ViewHelper for update script link
|
||||
* @internal
|
||||
*/
|
||||
class ReloadSqlDataViewHelper extends ActionViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $registryNamespace = 'extensionDataImport';
|
||||
|
||||
public function initializeArguments()
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('extension', 'array', 'Extension key', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a link to re-import the static SQL data of an extension
|
||||
*
|
||||
* @return string The rendered a tag
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$extension = $this->arguments['extension'];
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
|
||||
$staticSqlDataFile = $extension['siteRelPath'] . 'ext_tables_static+adt.sql';
|
||||
if (!file_exists(Environment::getPublicPath() . '/' . $staticSqlDataFile)) {
|
||||
return '<span class="btn btn-default disabled">' . $iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
|
||||
}
|
||||
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$oldMd5Hash = $registry->get(static::$registryNamespace, $staticSqlDataFile);
|
||||
|
||||
$md5HashIsEqual = true;
|
||||
// We used to only store "1" in the database when data was imported
|
||||
// No need to compare file content here and just show the reload icon
|
||||
if (!empty($oldMd5Hash) && $oldMd5Hash !== 1) {
|
||||
$currentMd5Hash = md5_file(Environment::getPublicPath() . '/' . $staticSqlDataFile);
|
||||
$md5HashIsEqual = $oldMd5Hash === $currentMd5Hash;
|
||||
}
|
||||
|
||||
if ($md5HashIsEqual) {
|
||||
$iconIdentifier = 'actions-database-reload';
|
||||
$languageKey = 'extensionList.databaseReload';
|
||||
} else {
|
||||
$iconIdentifier = 'actions-database-import';
|
||||
$languageKey = 'extensionList.databaseImport';
|
||||
}
|
||||
|
||||
$uriBuilder = $this->renderingContext->getControllerContext()->getUriBuilder();
|
||||
$uriBuilder->reset();
|
||||
$uri = $uriBuilder->uriFor('reloadExtensionData', ['extension' => $extension['key']], 'Action');
|
||||
$this->tag->addAttribute('href', $uri);
|
||||
$this->tag->addAttribute('title', LocalizationUtility::translate($languageKey, 'extensionmanager'));
|
||||
$this->tag->setContent($iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL)->render());
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\Link\ActionViewHelper;
|
||||
|
||||
/**
|
||||
* ViewHelper for displaying a remove extension link
|
||||
* @internal
|
||||
*/
|
||||
class RemoveExtensionViewHelper extends ActionViewHelper
|
||||
{
|
||||
/**
|
||||
* Initialize arguments
|
||||
*
|
||||
* @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('extension', 'array', '', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an install link
|
||||
*
|
||||
* @return string the rendered a tag
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$extension = $this->arguments['extension'];
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($extension['key'])) {
|
||||
return '<span class="btn btn-default disabled">' . $iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
|
||||
}
|
||||
if (
|
||||
!in_array($extension['type'], \TYPO3\CMS\Extensionmanager\Domain\Model\Extension::returnAllowedInstallTypes()) ||
|
||||
$extension['type'] === 'System'
|
||||
) {
|
||||
return '<span class="btn btn-default disabled">' . $iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
|
||||
}
|
||||
$uriBuilder = $this->renderingContext->getControllerContext()->getUriBuilder();
|
||||
$action = 'removeExtension';
|
||||
$uriBuilder->reset();
|
||||
$uriBuilder->setFormat('json');
|
||||
$uri = $uriBuilder->uriFor($action, [
|
||||
'extension' => $extension['key']
|
||||
], 'Action');
|
||||
$this->tag->addAttribute('href', $uri);
|
||||
$cssClass = 'removeExtension btn btn-default';
|
||||
$this->tag->addAttribute('class', $cssClass);
|
||||
$this->tag->addAttribute('title', \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('extensionList.remove', 'extensionmanager'));
|
||||
$this->tag->setContent($iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render());
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* 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\Extensionmanager\Domain\Model\Extension;
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\Link\ActionViewHelper;
|
||||
|
||||
/**
|
||||
* Display a link to show all versions of an extension
|
||||
* @internal
|
||||
*/
|
||||
class ShowExtensionVersionsViewHelper extends ActionViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'a';
|
||||
|
||||
/**
|
||||
* Initialize arguments
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('extension', Extension::class, '', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an install link
|
||||
*
|
||||
* @return string the rendered a tag
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
/** @var Extension $extension */
|
||||
$extension = $this->arguments['extension'];
|
||||
|
||||
$uriBuilder = $this->renderingContext->getControllerContext()->getUriBuilder();
|
||||
$action = 'showAllVersions';
|
||||
$uri = $uriBuilder->reset()->uriFor($action, [
|
||||
'extensionKey' => $extension->getExtensionKey(),
|
||||
], 'List');
|
||||
$this->tag->addAttribute('href', $uri);
|
||||
|
||||
// Set class
|
||||
$this->tag->addAttribute('class', 'versions-all ui-icon ui-icon-triangle-1-s');
|
||||
|
||||
$label = \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('extensionList.showAllVersions.label', 'extensionmanager');
|
||||
$this->tag->addAttribute('title', $label);
|
||||
$this->tag->setContent($label);
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\Link\ActionViewHelper;
|
||||
|
||||
/**
|
||||
* Display a deactivate / activate link
|
||||
* @internal
|
||||
*/
|
||||
class ToggleExtensionInstallationStateViewHelper extends ActionViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'a';
|
||||
|
||||
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager */
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
|
||||
*/
|
||||
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize arguments
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('extension', 'array', '', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an install link
|
||||
*
|
||||
* @return string the rendered a tag
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$extension = $this->arguments['extension'];
|
||||
// Early return if package is protected or is a runtime activated package and can not be unloaded
|
||||
/** @var \TYPO3\CMS\Core\Package\PackageManager $packageManager */
|
||||
$packageManager = $this->objectManager->get(PackageManager::class);
|
||||
$package = $packageManager->getPackage($extension['key']);
|
||||
if ($package->isProtected() || in_array($extension['key'], $GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$uriBuilder = $this->renderingContext->getControllerContext()->getUriBuilder();
|
||||
$action = 'toggleExtensionInstallationState';
|
||||
$uri = $uriBuilder->reset()->uriFor($action, [
|
||||
'extensionKey' => $extension['key']
|
||||
], 'Action');
|
||||
$this->tag->addAttribute('href', $uri);
|
||||
$label = $extension['installed'] ? 'deactivate' : 'activate';
|
||||
$this->tag->addAttribute('title', LocalizationUtility::translate('extensionList.' . $label, 'extensionmanager'));
|
||||
$icon = $extension['installed'] ? 'uninstall' : 'install';
|
||||
$this->tag->addAttribute('class', 'onClickMaskExtensionManager btn btn-default');
|
||||
|
||||
/** @var IconFactory $iconFactory */
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
$this->tag->setContent($iconFactory->getIcon('actions-system-extension-' . $icon, Icon::SIZE_SMALL)->render());
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* 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\MathUtility;
|
||||
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
|
||||
use TYPO3\CMS\Extensionmanager\Domain\Model\Dependency;
|
||||
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
|
||||
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
|
||||
|
||||
/**
|
||||
* Shows the version numbers of the TYPO3 dependency, if any
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Typo3DependencyViewHelper extends AbstractViewHelper
|
||||
{
|
||||
use CompileWithRenderStatic;
|
||||
|
||||
/**
|
||||
* As this ViewHelper renders HTML, the output must not be escaped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $escapeOutput = false;
|
||||
|
||||
/**
|
||||
* Initialize arguments
|
||||
*
|
||||
* @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
$this->registerArgument('extension', Extension::class, '', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the suitable TYPO3 versions of an extension
|
||||
*
|
||||
* @param array $arguments
|
||||
* @param \Closure $renderChildrenClosure
|
||||
* @param RenderingContextInterface $renderingContext
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
|
||||
{
|
||||
$extension = $arguments['extension'];
|
||||
/** @var Dependency $dependency */
|
||||
foreach ($extension->getDependencies() as $dependency) {
|
||||
if ($dependency->getIdentifier() === 'typo3') {
|
||||
$lowestVersion = $dependency->getLowestVersion();
|
||||
$highestVersion = $dependency->getHighestVersion();
|
||||
$cssClass = self::isVersionSuitable($lowestVersion, $highestVersion) ? 'success' : 'default';
|
||||
return
|
||||
'<span class="label label-' . $cssClass . '">'
|
||||
. htmlspecialchars($lowestVersion) . ' - ' . htmlspecialchars($highestVersion)
|
||||
. '</span>';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current TYPO3 version is suitable for the extension
|
||||
*
|
||||
* @param string $lowestVersion
|
||||
* @param string $highestVersion
|
||||
* @return bool
|
||||
*/
|
||||
protected static function isVersionSuitable($lowestVersion, $highestVersion)
|
||||
{
|
||||
$numericTypo3Version = VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getNumericTypo3Version());
|
||||
$numericLowestVersion = VersionNumberUtility::convertVersionNumberToInteger($lowestVersion);
|
||||
$numericHighestVersion = VersionNumberUtility::convertVersionNumberToInteger($highestVersion);
|
||||
return MathUtility::isIntegerInRange($numericTypo3Version, $numericLowestVersion, $numericHighestVersion);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Extensionmanager\ViewHelpers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\Link\ActionViewHelper;
|
||||
|
||||
/**
|
||||
* ViewHelper for update script link
|
||||
* @internal
|
||||
*/
|
||||
class UpdateScriptViewHelper extends ActionViewHelper
|
||||
{
|
||||
|
||||
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager */
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
|
||||
*/
|
||||
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* initialize arguments
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('extensionKey', 'string', 'Extension key', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a link to the update script screen if the extension has one
|
||||
*
|
||||
* @return string The rendered a tag
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$extensionKey = $this->arguments['extensionKey'];
|
||||
|
||||
// If the "class.ext_update.php" file exists, build link to the update script screen
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Utility\UpdateScriptUtility $updateScriptUtility */
|
||||
$updateScriptUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\UpdateScriptUtility::class);
|
||||
/** @var IconFactory $iconFactory */
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
if ($updateScriptUtility->checkUpdateScriptExists($extensionKey)) {
|
||||
$uriBuilder = $this->renderingContext->getControllerContext()->getUriBuilder();
|
||||
$action = 'show';
|
||||
$uri = $uriBuilder->reset()->uriFor(
|
||||
$action,
|
||||
['extensionKey' => $extensionKey],
|
||||
'UpdateScript'
|
||||
);
|
||||
$this->tag->addAttribute('href', $uri);
|
||||
$this->tag->addAttribute('title', \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('extensionList.update.script', 'extensionmanager'));
|
||||
$this->tag->setContent($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)->render());
|
||||
$tag = $this->tag->render();
|
||||
} else {
|
||||
return '<span class="btn btn-default disabled">' . $iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user