first commit

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

No files matched your search

@@ -0,0 +1,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
{
}
@@ -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
{
}
@@ -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
{
}
@@ -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 '';
}
}
@@ -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 '';
}
}
@@ -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();
}
}
@@ -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;
}
}