first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Authentication;
|
||||
|
||||
/*
|
||||
* 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\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
|
||||
use TYPO3\CMS\Core\Mail\MailMessage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Service\SessionService;
|
||||
|
||||
/**
|
||||
* Authenticates a user (currently comparing it through the install tool password, but could be extended)
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class AuthenticationService
|
||||
{
|
||||
/**
|
||||
* @var SessionService
|
||||
*/
|
||||
protected $sessionService;
|
||||
|
||||
/**
|
||||
* @param SessionService $sessionService
|
||||
*/
|
||||
public function __construct(SessionService $sessionService)
|
||||
{
|
||||
$this->sessionService = $sessionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks against a given password
|
||||
*
|
||||
* @param string $password
|
||||
* @return bool if authentication was successful, otherwise false
|
||||
*/
|
||||
public function loginWithPassword($password = null): bool
|
||||
{
|
||||
$validPassword = false;
|
||||
if ($password !== null && $password !== '') {
|
||||
$installToolPassword = $GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'];
|
||||
$hashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
|
||||
try {
|
||||
$hashInstance = $hashFactory->get($installToolPassword, 'BE');
|
||||
$validPassword = $hashInstance->checkPassword($password, $installToolPassword);
|
||||
} catch (InvalidPasswordHashException $invalidPasswordHashException) {
|
||||
// Given hash in global configuration is not a valid salted password
|
||||
if (md5($password) === $installToolPassword) {
|
||||
// Update configured install tool hash if it is still "MD5" and password matches
|
||||
// @todo: This should be removed in TYPO3 v10.0 with a dedicated breaking patch
|
||||
// @todo: Additionally, this code should check required hash updates and update the hash if needed
|
||||
$hashInstance = $hashFactory->getDefaultHashInstance('BE');
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$configurationManager->setLocalConfigurationValueByPath(
|
||||
'BE/installToolPassword',
|
||||
$hashInstance->getHashedPassword($password)
|
||||
);
|
||||
$validPassword = true;
|
||||
} else {
|
||||
// Still no valid hash instance could be found. Probably the stored hash used a mechanism
|
||||
// that is not available on current system. We throw the previous exception again to be
|
||||
// handled on a higher level. The install tool will render an according exception message
|
||||
// that links to the documentation.
|
||||
throw $invalidPasswordHashException;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($validPassword) {
|
||||
$this->sessionService->setAuthorized();
|
||||
$this->sendLoginSuccessfulMail();
|
||||
return true;
|
||||
}
|
||||
$this->sendLoginFailedMail();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If install tool login mail is set, send a mail for a successful login.
|
||||
*/
|
||||
protected function sendLoginSuccessfulMail()
|
||||
{
|
||||
$warningEmailAddress = $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'];
|
||||
if ($warningEmailAddress) {
|
||||
$mailMessage = GeneralUtility::makeInstance(MailMessage::class);
|
||||
$mailMessage
|
||||
->addTo($warningEmailAddress)
|
||||
->setSubject('Install Tool Login at \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\'')
|
||||
->addFrom($this->getSenderEmailAddress(), $this->getSenderEmailName())
|
||||
->setBody('There has been an Install Tool login at TYPO3 site'
|
||||
. ' \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\''
|
||||
. ' (' . GeneralUtility::getIndpEnv('HTTP_HOST') . ')'
|
||||
. ' from remote address \'' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . '\'')
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If install tool login mail is set, send a mail for a failed login.
|
||||
*/
|
||||
protected function sendLoginFailedMail()
|
||||
{
|
||||
$formValues = GeneralUtility::_GP('install');
|
||||
$warningEmailAddress = $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'];
|
||||
if ($warningEmailAddress) {
|
||||
$mailMessage = GeneralUtility::makeInstance(MailMessage::class);
|
||||
$mailMessage
|
||||
->addTo($warningEmailAddress)
|
||||
->setSubject('Install Tool Login ATTEMPT at \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\'')
|
||||
->addFrom($this->getSenderEmailAddress(), $this->getSenderEmailName())
|
||||
->setBody('There has been an Install Tool login attempt at TYPO3 site'
|
||||
. ' \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\''
|
||||
. ' (' . GeneralUtility::getIndpEnv('HTTP_HOST') . ')'
|
||||
. ' The last 5 characters of the MD5 hash of the password tried was \'' . substr(md5($formValues['password']), -5) . '\''
|
||||
. ' remote address was \'' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . '\'')
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sender address from configuration
|
||||
* ['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress']
|
||||
* If this setting is empty fall back to 'no-reply@example.com'
|
||||
*
|
||||
* @return string Returns an email address
|
||||
*/
|
||||
protected function getSenderEmailAddress()
|
||||
{
|
||||
return !empty($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'])
|
||||
? $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress']
|
||||
: 'no-reply@example.com';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets sender name from configuration
|
||||
* ['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']
|
||||
* If this setting is empty, it falls back to a default string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getSenderEmailName()
|
||||
{
|
||||
return !empty($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'])
|
||||
? $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']
|
||||
: 'TYPO3 CMS install tool';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\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\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Cache\CacheManager;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Service\LanguagePackService;
|
||||
|
||||
/**
|
||||
* Core function for updating language packs
|
||||
*/
|
||||
class LanguagePackCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Configure the command by defining the name, options and arguments
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setAliases(['lang:language:update']);
|
||||
$this->setDescription('Update the language files of all activated extensions')
|
||||
->addArgument(
|
||||
'locales',
|
||||
InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
|
||||
'Provide iso codes separated by space to update only selected language packs. Example `bin/typo3 language:update de ja`.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update language packs of all active languages for all active extensions
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if ($input->hasArgument('command') && substr_count($input->getArgument('command'), ':') === 2) {
|
||||
$message = 'bin/typo3 lang:language:update is deprecated, use bin/typo3 language:update instead';
|
||||
$output->writeln('<error>' . $message . '</error>');
|
||||
trigger_error($message, E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
|
||||
|
||||
try {
|
||||
$isos = $input->getArgument('locales');
|
||||
} catch (\Exception $e) {
|
||||
$isos = [];
|
||||
}
|
||||
if (empty($isos)) {
|
||||
$isos = $languagePackService->getActiveLanguages();
|
||||
}
|
||||
|
||||
if ($output->isVerbose()) {
|
||||
$output->writeln(sprintf(
|
||||
'<info>Updating language packs of all activated extensions for locale(s) "%s"</info>',
|
||||
implode('", "', $isos)
|
||||
));
|
||||
}
|
||||
|
||||
$extensions = $languagePackService->getExtensionLanguagePackDetails();
|
||||
|
||||
if (!$output->isVerbose()) {
|
||||
$progressBarOutput = new NullOutput();
|
||||
} else {
|
||||
$progressBarOutput = $output;
|
||||
}
|
||||
$progressBar = new ProgressBar($progressBarOutput, count($isos) * count($extensions));
|
||||
$languagePackService->updateMirrorBaseUrl();
|
||||
foreach ($isos as $iso) {
|
||||
foreach ($extensions as $extension) {
|
||||
$languagePackService->languagePackDownload($extension['key'], $iso);
|
||||
$progressBar->advance();
|
||||
}
|
||||
}
|
||||
$languagePackService->setLastUpdatedIsoCode($isos);
|
||||
$progressBar->finish();
|
||||
|
||||
// Flush language cache
|
||||
GeneralUtility::makeInstance(CacheManager::class)->getCache('l10n')->flush();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\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\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
|
||||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Service\UpgradeWizardsService;
|
||||
use TYPO3\CMS\Install\Updates\ChattyInterface;
|
||||
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
|
||||
|
||||
/**
|
||||
* Upgrade wizard command for listing wizards
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UpgradeWizardListCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var UpgradeWizardsService
|
||||
*/
|
||||
private $upgradeWizardsService;
|
||||
|
||||
/**
|
||||
* @var OutputInterface|\Symfony\Component\Console\Style\StyleInterface
|
||||
*/
|
||||
private $output;
|
||||
|
||||
/**
|
||||
* @var InputInterface
|
||||
*/
|
||||
private $input;
|
||||
|
||||
/**
|
||||
* Bootstrap running of upgradeWizards
|
||||
*/
|
||||
protected function bootstrap(): void
|
||||
{
|
||||
Bootstrap::loadTypo3LoadedExtAndExtLocalconf(false);
|
||||
Bootstrap::unsetReservedGlobalVariables();
|
||||
Bootstrap::loadBaseTca(false);
|
||||
Bootstrap::loadExtTables(false);
|
||||
Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
|
||||
Bootstrap::initializeBackendAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the command by defining the name, options and arguments
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setDescription('List available upgrade wizards.')
|
||||
->addOption(
|
||||
'all',
|
||||
'a',
|
||||
InputOption::VALUE_NONE,
|
||||
'Include wizards already done.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List available upgrade wizards. If -all is given, already done wizards are listed, too.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->output = new SymfonyStyle($input, $output);
|
||||
$this->input = $input;
|
||||
$this->bootstrap();
|
||||
$this->upgradeWizardsService = new UpgradeWizardsService();
|
||||
|
||||
$result = 0;
|
||||
$wizards = [];
|
||||
$all = $input->getOption('all');
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $wizardToExecute) {
|
||||
$upgradeWizard = $this->getWizard($wizardToExecute, $identifier, (bool)$all);
|
||||
if ($upgradeWizard !== null) {
|
||||
$wizardInfo = [
|
||||
'identifier' => $upgradeWizard->getIdentifier(),
|
||||
'title' => $upgradeWizard->getTitle(),
|
||||
'description' => wordwrap($upgradeWizard->getDescription()),
|
||||
];
|
||||
if ($all === true) {
|
||||
$wizardInfo['status'] = $this->upgradeWizardsService->isWizardDone($identifier) ? 'DONE' : 'AVAILABLE';
|
||||
}
|
||||
$wizards[] = $wizardInfo;
|
||||
}
|
||||
}
|
||||
if (empty($wizards)) {
|
||||
$this->output->success('No wizards available.');
|
||||
} else {
|
||||
if ($all === true) {
|
||||
$this->output->table(['Identifier', 'Title', 'Description', 'Status'], $wizards);
|
||||
} else {
|
||||
$this->output->table(['Identifier', 'Title', 'Description'], $wizards);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Wizard instance by class name and identifier
|
||||
* Returns null if wizard is already done
|
||||
*
|
||||
* @param string $className
|
||||
* @param string $identifier
|
||||
* @param bool $all
|
||||
* @return \TYPO3\CMS\Install\Updates\UpgradeWizardInterface|null
|
||||
*/
|
||||
protected function getWizard(string $className, string $identifier, $all = false): ?UpgradeWizardInterface
|
||||
{
|
||||
// already done
|
||||
if (!$all && $this->upgradeWizardsService->isWizardDone($identifier)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$wizardInstance = GeneralUtility::makeInstance($className);
|
||||
if ($wizardInstance instanceof ChattyInterface) {
|
||||
$wizardInstance->setOutput($this->output);
|
||||
}
|
||||
|
||||
if (!($wizardInstance instanceof UpgradeWizardInterface)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return !$all ? $wizardInstance->updateNecessary() ? $wizardInstance : null : $wizardInstance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\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\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
|
||||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Service\UpgradeWizardsService;
|
||||
use TYPO3\CMS\Install\Updates\ChattyInterface;
|
||||
use TYPO3\CMS\Install\Updates\ConfirmableInterface;
|
||||
use TYPO3\CMS\Install\Updates\PrerequisiteCollection;
|
||||
use TYPO3\CMS\Install\Updates\RepeatableInterface;
|
||||
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
|
||||
|
||||
/**
|
||||
* Upgrade wizard command for running wizards
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UpgradeWizardRunCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var UpgradeWizardsService
|
||||
*/
|
||||
private $upgradeWizardsService;
|
||||
|
||||
/**
|
||||
* @var OutputInterface|\Symfony\Component\Console\Style\StyleInterface
|
||||
*/
|
||||
private $output;
|
||||
|
||||
/**
|
||||
* @var InputInterface
|
||||
*/
|
||||
private $input;
|
||||
|
||||
/**
|
||||
* Bootstrap running of upgrade wizard,
|
||||
* ensure database is utf-8
|
||||
*/
|
||||
protected function bootstrap(): void
|
||||
{
|
||||
Bootstrap::loadTypo3LoadedExtAndExtLocalconf(false);
|
||||
Bootstrap::unsetReservedGlobalVariables();
|
||||
Bootstrap::loadBaseTca(false);
|
||||
Bootstrap::loadExtTables(false);
|
||||
Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
|
||||
Bootstrap::initializeBackendAuthentication();
|
||||
$this->upgradeWizardsService = new UpgradeWizardsService();
|
||||
$this->upgradeWizardsService->isDatabaseCharsetUtf8() ?: $this->upgradeWizardsService->setDatabaseCharsetUtf8();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the command by defining the name, options and arguments
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setDescription('Run upgrade wizard. Without arguments all available wizards will be run.')
|
||||
->addArgument(
|
||||
'wizardName',
|
||||
InputArgument::OPTIONAL
|
||||
)->setHelp(
|
||||
'This command allows running upgrade wizards on CLI. To run a single wizard add the ' .
|
||||
'identifier of the wizard as argument. The identifier of the wizard is the name it is ' .
|
||||
'registered with in ext_localconf.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update language packs of all active languages for all active extensions
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->output = new SymfonyStyle($input, $output);
|
||||
$this->input = $input;
|
||||
$this->bootstrap();
|
||||
|
||||
$result = 0;
|
||||
if ($input->getArgument('wizardName')) {
|
||||
$wizardToExecute = $input->getArgument('wizardName');
|
||||
if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute])) {
|
||||
$className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute];
|
||||
$upgradeWizard = $this->getWizard($className, $wizardToExecute);
|
||||
if ($upgradeWizard !== null) {
|
||||
$prerequisitesFulfilled = $this->handlePrerequisites([$upgradeWizard]);
|
||||
if ($prerequisitesFulfilled === true) {
|
||||
$result = $this->runSingleWizard($upgradeWizard);
|
||||
} else {
|
||||
$result = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->output->error('No such wizard: ' . $wizardToExecute);
|
||||
$result = 1;
|
||||
}
|
||||
} else {
|
||||
$result = $this->runAllWizards();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Wizard instance by class name and identifier
|
||||
* Returns null if wizard is already done
|
||||
*
|
||||
* @param $className
|
||||
* @param $identifier
|
||||
* @return \TYPO3\CMS\Install\Updates\UpgradeWizardInterface|null
|
||||
*/
|
||||
protected function getWizard(string $className, string $identifier): ?UpgradeWizardInterface
|
||||
{
|
||||
// already done
|
||||
if ($this->upgradeWizardsService->isWizardDone($identifier)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$wizardInstance = GeneralUtility::makeInstance($className);
|
||||
if ($wizardInstance instanceof ChattyInterface) {
|
||||
$wizardInstance->setOutput($this->output);
|
||||
}
|
||||
|
||||
if (!($wizardInstance instanceof UpgradeWizardInterface)) {
|
||||
$this->output->writeln(
|
||||
'Wizard ' .
|
||||
$identifier .
|
||||
' needs to be manually run from the install tool, as it does not implement ' .
|
||||
UpgradeWizardInterface::class
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($wizardInstance->updateNecessary()) {
|
||||
return $wizardInstance;
|
||||
}
|
||||
if ($wizardInstance instanceof RepeatableInterface) {
|
||||
$this->output->note('Wizard ' . $identifier . ' does not need to make changes.');
|
||||
} else {
|
||||
$this->output->note('Wizard ' . $identifier . ' does not need to make changes. Marking wizard as done.');
|
||||
$this->upgradeWizardsService->markWizardAsDone($identifier);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles prerequisites of update wizards, allows a more flexible definition and declaration of dependencies
|
||||
* Currently implemented prerequisites include "database needs to be up-to-date" and "referenceIndex needs to be up-
|
||||
* to-date"
|
||||
* At the moment the install tool automatically displays the database updates when necessary but can't do more
|
||||
* prerequisites
|
||||
*
|
||||
* @param UpgradeWizardInterface[] $instances
|
||||
* @return bool
|
||||
*/
|
||||
protected function handlePrerequisites(array $instances): bool
|
||||
{
|
||||
$prerequisites = GeneralUtility::makeInstance(PrerequisiteCollection::class);
|
||||
foreach ($instances as $instance) {
|
||||
foreach ($instance->getPrerequisites() as $prerequisite) {
|
||||
$prerequisites->add($prerequisite);
|
||||
}
|
||||
}
|
||||
$result = true;
|
||||
foreach ($prerequisites as $prerequisite) {
|
||||
if ($prerequisite instanceof ChattyInterface) {
|
||||
$prerequisite->setOutput($this->output);
|
||||
}
|
||||
if (!$prerequisite->isFulfilled()) {
|
||||
$this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" not fulfilled, will ensure.');
|
||||
$result = $prerequisite->ensure();
|
||||
if ($result === false) {
|
||||
$this->output->error(
|
||||
'<error>Error running ' .
|
||||
$prerequisite->getTitle() .
|
||||
'. Please ensure this prerequisite manually and try again.</error>'
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" fulfilled.');
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UpgradeWizardInterface $instance
|
||||
* @return int
|
||||
*/
|
||||
protected function runSingleWizard(
|
||||
UpgradeWizardInterface $instance
|
||||
): int {
|
||||
$this->output->title('Running Wizard ' . $instance->getTitle());
|
||||
if ($instance instanceof ConfirmableInterface) {
|
||||
$confirmation = $instance->getConfirmation();
|
||||
$defaultString = $confirmation->getDefaultValue() ? 'Y/n' : 'y/N';
|
||||
$question = new ConfirmationQuestion(
|
||||
sprintf(
|
||||
'<info>%s</info>' . LF . '%s' . LF . '%s %s (%s)',
|
||||
$confirmation->getTitle(),
|
||||
$confirmation->getMessage(),
|
||||
$confirmation->getConfirm(),
|
||||
$confirmation->getDeny(),
|
||||
$defaultString
|
||||
),
|
||||
$confirmation->getDefaultValue()
|
||||
);
|
||||
$helper = $this->getHelper('question');
|
||||
if (!$helper->ask($this->input, $this->output, $question)) {
|
||||
if ($confirmation->isRequired()) {
|
||||
$this->output->error('You have to acknowledge this wizard to continue');
|
||||
return 1;
|
||||
}
|
||||
if ($instance instanceof RepeatableInterface) {
|
||||
$this->output->note('No changes applied.');
|
||||
} else {
|
||||
$this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
|
||||
$this->output->note('No changes applied, marking wizard as done.');
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ($instance->executeUpdate()) {
|
||||
$this->output->success('Successfully ran wizard ' . $instance->getTitle());
|
||||
if (!$instance instanceof RepeatableInterface) {
|
||||
$this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
$this->output->error('<error>Something went wrong while running ' . $instance->getTitle() . '</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of registered upgrade wizards.
|
||||
*
|
||||
* @return int 0 if all wizards were successful, 1 on error
|
||||
*/
|
||||
public function runAllWizards(): int
|
||||
{
|
||||
$returnCode = 0;
|
||||
$wizardInstances = [];
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $class) {
|
||||
$wizardInstances[] = $this->getWizard($class, $identifier);
|
||||
}
|
||||
$wizardInstances = array_filter($wizardInstances);
|
||||
if (count($wizardInstances) > 0) {
|
||||
$prerequisitesResult = $this->handlePrerequisites($wizardInstances);
|
||||
if ($prerequisitesResult === false) {
|
||||
$returnCode = 1;
|
||||
$this->output->error('Error handling prerequisites, aborting.');
|
||||
} else {
|
||||
$this->output->title('Found ' . count($wizardInstances) . ' wizard(s) to run.');
|
||||
foreach ($wizardInstances as $wizardInstance) {
|
||||
$result = $this->runSingleWizard($wizardInstance);
|
||||
if ($result > 0) {
|
||||
$returnCode = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->output->success('No wizards left to run.');
|
||||
}
|
||||
return $returnCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Composer;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 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 Composer\Script\Event;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScripts\EntryPoint;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScriptsRegistration;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\ScriptDispatcher;
|
||||
|
||||
/**
|
||||
* Hook into Composer build to generate TYPO3 install tool entry script
|
||||
*
|
||||
* @internal only used within TYPO3's composer mode and not part of TYPO3 Core API.
|
||||
*/
|
||||
class InstallerScripts implements InstallerScriptsRegistration
|
||||
{
|
||||
/**
|
||||
* @param Event $event
|
||||
* @param ScriptDispatcher $scriptDispatcher
|
||||
*/
|
||||
public static function register(Event $event, ScriptDispatcher $scriptDispatcher)
|
||||
{
|
||||
$scriptDispatcher->addInstallerScript(
|
||||
new EntryPoint(
|
||||
dirname(__DIR__, 2) . '/Resources/Private/Php/install.php',
|
||||
'typo3/install.php'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/*
|
||||
* 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 custom preset class implements common preset code
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
abstract class AbstractCustomPreset extends AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset, always set to "Custom"
|
||||
*/
|
||||
protected $name = 'Custom';
|
||||
|
||||
/**
|
||||
* @var bool TRUE if custom preset is active
|
||||
*/
|
||||
protected $isActive = false;
|
||||
|
||||
/**
|
||||
* @var int Priority of custom prefix is usually the lowest
|
||||
*/
|
||||
protected $priority = 10;
|
||||
|
||||
/**
|
||||
* Whether custom preset is active is set by feature
|
||||
*
|
||||
* @return bool TRUE if custom preset is active
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
return $this->isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark preset as active.
|
||||
* The custom features do not know by itself if they are
|
||||
* active or not since the configuration options may overlay
|
||||
* with other presets.
|
||||
* Marking the custom preset as active is therefor taken care
|
||||
* off by the feature itself if no other preset is active.
|
||||
*/
|
||||
public function setActive()
|
||||
{
|
||||
$this->isActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom configuration is always available
|
||||
*
|
||||
* @return bool TRUE
|
||||
*/
|
||||
public function isAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration values is used in fluid to show configuration options.
|
||||
* They are fetched from LocalConfiguration / DefaultConfiguration and
|
||||
* merged with given $postValues.
|
||||
*
|
||||
* @return array Configuration values needed to activate prefix
|
||||
*/
|
||||
public function getConfigurationValues()
|
||||
{
|
||||
$configurationValues = [];
|
||||
foreach ($this->configurationValues as $configurationKey => $configurationValue) {
|
||||
if (isset($this->postValues['enable'])
|
||||
&& $this->postValues['enable'] === $this->name
|
||||
&& isset($this->postValues[$this->name][$configurationKey])
|
||||
) {
|
||||
$currentValue = $this->postValues[$this->name][$configurationKey];
|
||||
} else {
|
||||
$currentValue = $this->configurationManager->getConfigurationValueByPath($configurationKey);
|
||||
}
|
||||
$configurationValues[$configurationKey] = $currentValue;
|
||||
}
|
||||
return $configurationValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Abstract feature class implements common code
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
abstract class AbstractFeature
|
||||
{
|
||||
/**
|
||||
* @var string Name of feature
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* @var array List of preset classes
|
||||
*/
|
||||
protected $presetRegistry = [];
|
||||
|
||||
/**
|
||||
* @var array Holds instances of presets
|
||||
*/
|
||||
protected $presetInstances = [];
|
||||
|
||||
/**
|
||||
* @var array List of $POST values
|
||||
*/
|
||||
protected $postValues = [];
|
||||
|
||||
/**
|
||||
* Initialize presets of feature
|
||||
*
|
||||
* @param array $postValues List of $POST values of this feature
|
||||
* @throws Exception
|
||||
*/
|
||||
public function initializePresets(array $postValues)
|
||||
{
|
||||
// Give feature sub array of $POST values to preset and set to own property
|
||||
$featurePostValues = [];
|
||||
if (!empty($postValues[$this->name])) {
|
||||
$featurePostValues = $postValues[$this->name];
|
||||
}
|
||||
$this->postValues = $featurePostValues;
|
||||
|
||||
$isNonCustomPresetActive = false;
|
||||
$customPresetFound = false;
|
||||
foreach ($this->presetRegistry as $presetClass) {
|
||||
/** @var PresetInterface $presetInstance */
|
||||
$presetInstance = GeneralUtility::makeInstance($presetClass);
|
||||
if (!($presetInstance instanceof PresetInterface)) {
|
||||
throw new Exception(
|
||||
'Preset ' . $presetClass . ' does not implement PresetInterface',
|
||||
1378644821
|
||||
);
|
||||
}
|
||||
|
||||
$presetInstance->setPostValues($featurePostValues);
|
||||
|
||||
// Custom preset is set active if no preset before is active
|
||||
if ($presetInstance->isActive()) {
|
||||
$isNonCustomPresetActive = true;
|
||||
}
|
||||
if ($presetInstance instanceof CustomPresetInterface
|
||||
&& !$isNonCustomPresetActive
|
||||
) {
|
||||
// Throw Exception if two custom presets are registered
|
||||
if ($customPresetFound === true) {
|
||||
throw new Exception(
|
||||
'Preset ' . $presetClass . ' implements CustomPresetInterface, but another'
|
||||
. ' custom preset is already registered',
|
||||
1378645039
|
||||
);
|
||||
}
|
||||
|
||||
/** @var CustomPresetInterface $presetInstance */
|
||||
$presetInstance->setActive();
|
||||
$customPresetFound = true;
|
||||
}
|
||||
|
||||
$this->presetInstances[] = $presetInstance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return presets ordered by priority
|
||||
*
|
||||
* @return array<PresetInterface>
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getPresetsOrderedByPriority()
|
||||
{
|
||||
if (empty($this->presetInstances)) {
|
||||
throw new Exception(
|
||||
'Presets not initialized',
|
||||
1378645155
|
||||
);
|
||||
}
|
||||
$orderedPresets = [];
|
||||
foreach ($this->presetInstances as $presetInstance) {
|
||||
/** @var PresetInterface $presetInstance */
|
||||
$orderedPresets[$presetInstance->getPriority()] = $presetInstance;
|
||||
}
|
||||
krsort($orderedPresets, SORT_NUMERIC);
|
||||
return $orderedPresets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return name of feature
|
||||
*
|
||||
* @return string Name of feature
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/*
|
||||
* 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\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Abstract preset class implements common preset code
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
abstract class AbstractPreset implements PresetInterface
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Configuration\ConfigurationManager
|
||||
*/
|
||||
protected $configurationManager;
|
||||
|
||||
/**
|
||||
* @var string Name of preset, must be set by extending classes
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* @var int Default priority of preset
|
||||
*/
|
||||
protected $priority = 50;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [];
|
||||
|
||||
/**
|
||||
* @var array List of $POST values
|
||||
*/
|
||||
protected $postValues = [];
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager
|
||||
*/
|
||||
public function __construct(ConfigurationManager $configurationManager = null)
|
||||
{
|
||||
$this->configurationManager = $configurationManager ?: GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set POST values
|
||||
*
|
||||
* @param array $postValues Post values of feature
|
||||
* @return mixed
|
||||
*/
|
||||
public function setPostValues(array $postValues)
|
||||
{
|
||||
$this->postValues = $postValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for isAvailable, used in fluid
|
||||
*
|
||||
* @return bool TRUE if preset is available
|
||||
*/
|
||||
public function getIsAvailable()
|
||||
{
|
||||
return $this->isAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is preset is currently active on the system
|
||||
*
|
||||
* @return bool TRUE if preset is active
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
$isActive = true;
|
||||
foreach ($this->configurationValues as $configurationKey => $configurationValue) {
|
||||
try {
|
||||
$currentValue = $this->configurationManager->getConfigurationValueByPath($configurationKey);
|
||||
} catch (MissingArrayPathException $e) {
|
||||
$currentValue = null;
|
||||
}
|
||||
if ($currentValue !== $configurationValue) {
|
||||
$isActive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for isActive, used in fluid
|
||||
*
|
||||
* @return bool TRUE if preset is active
|
||||
*/
|
||||
public function getIsActive()
|
||||
{
|
||||
return $this->isActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name of preset
|
||||
*
|
||||
* @return string Name
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get priority of preset
|
||||
*
|
||||
* @return int Priority, usually between 0 and 100
|
||||
*/
|
||||
public function getPriority()
|
||||
{
|
||||
return $this->priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration values to activate prefix
|
||||
*
|
||||
* @return array Configuration values needed to activate prefix
|
||||
*/
|
||||
public function getConfigurationValues()
|
||||
{
|
||||
return $this->configurationValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Context;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Context feature sets development / production settings
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class ContextFeature extends Configuration\AbstractFeature implements Configuration\FeatureInterface
|
||||
{
|
||||
/**
|
||||
* @var string Name of feature
|
||||
*/
|
||||
protected $name = 'Context';
|
||||
|
||||
/**
|
||||
* @var array List of preset classes
|
||||
*/
|
||||
protected $presetRegistry = [
|
||||
\TYPO3\CMS\Install\Configuration\Context\LivePreset::class,
|
||||
\TYPO3\CMS\Install\Configuration\Context\DebugPreset::class,
|
||||
\TYPO3\CMS\Install\Configuration\Context\CustomPreset::class,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Context;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Custom preset is a fallback if no other preset fits
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class CustomPreset extends Configuration\AbstractCustomPreset implements Configuration\CustomPresetInterface
|
||||
{
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'BE/debug' => '',
|
||||
'FE/debug' => '',
|
||||
'SYS/devIPmask' => '',
|
||||
'SYS/displayErrors' => '',
|
||||
'SYS/systemLogLevel' => '',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Context;
|
||||
|
||||
/*
|
||||
* 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\Log\LogLevel;
|
||||
use TYPO3\CMS\Core\Log\Writer\FileWriter;
|
||||
use TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Debug preset
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class DebugPreset extends Configuration\AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'Debug';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 50;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'BE/debug' => true,
|
||||
'FE/debug' => true,
|
||||
'SYS/devIPmask' => '*',
|
||||
'SYS/displayErrors' => 1,
|
||||
'SYS/systemLogLevel' => 0,
|
||||
// Values below are not available in UI
|
||||
'LOG/TYPO3/CMS/deprecations/writerConfiguration/' . LogLevel::NOTICE . '/' . FileWriter::class . '/disabled' => false,
|
||||
// E_WARNING | E_RECOVERABLE_ERROR | E_DEPRECATED
|
||||
'SYS/exceptionalErrors' => 12290,
|
||||
];
|
||||
|
||||
/**
|
||||
* Development preset is always available
|
||||
*
|
||||
* @return bool Always TRUE
|
||||
*/
|
||||
public function isAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If context is set to development, priority
|
||||
* of this preset is raised.
|
||||
*
|
||||
* @return int Priority of preset
|
||||
*/
|
||||
public function getPriority()
|
||||
{
|
||||
$context = \TYPO3\CMS\Core\Utility\GeneralUtility::getApplicationContext();
|
||||
$priority = $this->priority;
|
||||
if ($context->isDevelopment()) {
|
||||
$priority = $priority + 20;
|
||||
}
|
||||
return $priority;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Context;
|
||||
|
||||
/*
|
||||
* 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\Log\LogLevel;
|
||||
use TYPO3\CMS\Core\Log\Writer\FileWriter;
|
||||
use TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Live preset
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class LivePreset extends Configuration\AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'Live';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 50;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'BE/debug' => false,
|
||||
'FE/debug' => false,
|
||||
'SYS/devIPmask' => '',
|
||||
'SYS/displayErrors' => 0,
|
||||
'SYS/systemLogLevel' => 2,
|
||||
// Values below are not available in UI
|
||||
'LOG/TYPO3/CMS/deprecations/writerConfiguration/' . LogLevel::NOTICE . '/' . FileWriter::class . '/disabled' => true,
|
||||
// E_RECOVERABLE_ERROR
|
||||
'SYS/exceptionalErrors' => 4096,
|
||||
];
|
||||
|
||||
/**
|
||||
* Production preset is always available
|
||||
*
|
||||
* @return bool Always TRUE
|
||||
*/
|
||||
public function isAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If context is set to production, priority
|
||||
* of this preset is raised.
|
||||
*
|
||||
* @return int Priority of preset
|
||||
*/
|
||||
public function getPriority()
|
||||
{
|
||||
$context = \TYPO3\CMS\Core\Utility\GeneralUtility::getApplicationContext();
|
||||
$priority = $this->priority;
|
||||
if ($context->isProduction()) {
|
||||
$priority = $priority + 20;
|
||||
}
|
||||
return $priority;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Custom preset interface
|
||||
*
|
||||
* Interface for presets not caught by other presets.
|
||||
* Represents "custom" configuration options of a feature.
|
||||
*
|
||||
* There must be only one custom preset per feature!
|
||||
*/
|
||||
interface CustomPresetInterface extends PresetInterface
|
||||
{
|
||||
/**
|
||||
* Mark preset as active.
|
||||
* The custom features do not know by itself if they are
|
||||
* active or not since the configuration options may overlay
|
||||
* with other presets.
|
||||
* Marking the custom preset as active is therefor taken care
|
||||
* off by the feature itself if no other preset is active.
|
||||
*/
|
||||
public function setActive();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/*
|
||||
* 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 configuration exception
|
||||
*/
|
||||
class Exception extends \TYPO3\CMS\Install\Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/*
|
||||
* 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 feature representation handles preset classes.
|
||||
*/
|
||||
interface FeatureInterface
|
||||
{
|
||||
/**
|
||||
* Initialize presets
|
||||
*
|
||||
* @param array $postValues List of $POST values of this feature
|
||||
*/
|
||||
public function initializePresets(array $postValues);
|
||||
|
||||
/**
|
||||
* Get list of presets ordered by priority
|
||||
*
|
||||
* @return array<PresetInterface>
|
||||
*/
|
||||
public function getPresetsOrderedByPriority();
|
||||
|
||||
/**
|
||||
* Get name of feature
|
||||
*
|
||||
* @return string Name
|
||||
*/
|
||||
public function getName();
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration\Context\ContextFeature;
|
||||
use TYPO3\CMS\Install\Configuration\Image\ImageFeature;
|
||||
use TYPO3\CMS\Install\Configuration\Mail\MailFeature;
|
||||
use TYPO3\CMS\Install\Configuration\PasswordHashing\PasswordHashingFeature;
|
||||
|
||||
/**
|
||||
* Instantiate and configure all known features and presets
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class FeatureManager
|
||||
{
|
||||
/**
|
||||
* @var array List of feature class names
|
||||
*/
|
||||
protected $featureRegistry = [
|
||||
ContextFeature::class,
|
||||
ImageFeature::class,
|
||||
MailFeature::class,
|
||||
PasswordHashingFeature::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Get initialized list of features with possible presets
|
||||
*
|
||||
* @param array $postValues List of $POST values
|
||||
* @return FeatureInterface[]
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getInitializedFeatures(array $postValues = [])
|
||||
{
|
||||
$features = [];
|
||||
foreach ($this->featureRegistry as $featureClass) {
|
||||
/** @var FeatureInterface $featureInstance */
|
||||
$featureInstance = GeneralUtility::makeInstance($featureClass);
|
||||
if (!($featureInstance instanceof FeatureInterface)) {
|
||||
throw new Exception(
|
||||
'Feature ' . $featureClass . ' does not implement FeatureInterface',
|
||||
1378644593
|
||||
);
|
||||
}
|
||||
$featureInstance->initializePresets($postValues);
|
||||
$features[] = $featureInstance;
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration values to be set to LocalConfiguration from
|
||||
* list of selected $POST feature presets
|
||||
*
|
||||
* @param array $postValues List of $POST values
|
||||
* @return array List of configuration values
|
||||
*/
|
||||
public function getConfigurationForSelectedFeaturePresets(array $postValues)
|
||||
{
|
||||
$localConfigurationValuesToSet = [];
|
||||
$features = $this->getInitializedFeatures($postValues);
|
||||
foreach ($features as $feature) {
|
||||
/** @var FeatureInterface $feature */
|
||||
$featureName = $feature->getName();
|
||||
$presets = $feature->getPresetsOrderedByPriority();
|
||||
foreach ($presets as $preset) {
|
||||
/** @var PresetInterface $preset */
|
||||
$presetName = $preset->getName();
|
||||
if (!empty($postValues[$featureName]['enable'])
|
||||
&& $postValues[$featureName]['enable'] === $presetName
|
||||
&& (!$preset->isActive() || $preset instanceof CustomPresetInterface)
|
||||
) {
|
||||
$localConfigurationValuesToSet = array_merge(
|
||||
$localConfigurationValuesToSet,
|
||||
$preset->getConfigurationValues()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $localConfigurationValuesToSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle through features and get settings. First matching
|
||||
* preset (highest priority) will be selected.
|
||||
*
|
||||
* @return array Configuration settings
|
||||
*/
|
||||
public function getBestMatchingConfigurationForAllFeatures()
|
||||
{
|
||||
$localConfigurationValuesToSet = [];
|
||||
$features = $this->getInitializedFeatures([]);
|
||||
foreach ($features as $feature) {
|
||||
/** @var FeatureInterface $feature */
|
||||
$presets = $feature->getPresetsOrderedByPriority();
|
||||
foreach ($presets as $preset) {
|
||||
// Only choose "normal" presets, no custom presets
|
||||
if ($preset instanceof CustomPresetInterface) {
|
||||
break;
|
||||
}
|
||||
|
||||
/** @var PresetInterface $preset */
|
||||
if ($preset->isAvailable()) {
|
||||
$localConfigurationValuesToSet = array_merge(
|
||||
$localConfigurationValuesToSet,
|
||||
$preset->getConfigurationValues()
|
||||
);
|
||||
// Setting for this feature done, go to next feature
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $localConfigurationValuesToSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Image;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Abstract class implements common image preset code
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
abstract class AbstractImagePreset extends Configuration\AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var array Default paths to search for executable, with trailing slash
|
||||
*/
|
||||
protected $defaultExecutableSearchPaths = [
|
||||
'/usr/local/bin/',
|
||||
'/opt/local/bin/',
|
||||
'/usr/bin/',
|
||||
'/usr/X11R6/bin/',
|
||||
'/opt/bin/',
|
||||
'C:/php/ImageMagick/',
|
||||
'C:/php/GraphicsMagick/',
|
||||
'C:/apache/ImageMagick/',
|
||||
'C:/apache/GraphicsMagick/',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string Absolute path with found executable
|
||||
*/
|
||||
protected $foundPath = '';
|
||||
|
||||
/**
|
||||
* Path where executable was found
|
||||
*
|
||||
* @return string Fonud path
|
||||
*/
|
||||
public function getFoundPath()
|
||||
{
|
||||
return $this->foundPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is preset is currently active on the system.
|
||||
* Overwrites parent method to ignore processor_path and processor_path_lzw settings
|
||||
*
|
||||
* @return bool TRUE if preset is active
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
$isActive = true;
|
||||
foreach ($this->configurationValues as $configurationKey => $configurationValue) {
|
||||
if ($configurationKey !== 'GFX/processor_path'
|
||||
&& $configurationKey !== 'GFX/processor_path_lzw'
|
||||
) {
|
||||
$currentValue = $this->configurationManager->getConfigurationValueByPath($configurationKey);
|
||||
if ($currentValue !== $configurationValue) {
|
||||
$isActive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out if GraphicsMagick is available
|
||||
*
|
||||
* @return bool TRUE if GraphicsMagick executable is found in path
|
||||
*/
|
||||
public function isAvailable()
|
||||
{
|
||||
$searchPaths = $this->getSearchPaths();
|
||||
return $this->findExecutableInPath($searchPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration values to activate prefix
|
||||
*
|
||||
* @return array Configuration values needed to activate prefix
|
||||
*/
|
||||
public function getConfigurationValues()
|
||||
{
|
||||
$this->findExecutableInPath($this->getSearchPaths());
|
||||
$configurationValues = $this->configurationValues;
|
||||
$configurationValues['GFX/processor_path'] = $this->getFoundPath();
|
||||
$configurationValues['GFX/processor_path_lzw'] = $this->getFoundPath();
|
||||
return $configurationValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find executable in path, wrapper for specific ImageMagick/GraphicsMagick find methods.
|
||||
*
|
||||
* @param array $searchPaths
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function findExecutableInPath(array $searchPaths);
|
||||
|
||||
/**
|
||||
* Get list of paths to search for image handling executables
|
||||
*
|
||||
* @return array List of paths to search for
|
||||
*/
|
||||
protected function getSearchPaths()
|
||||
{
|
||||
$searchPaths = $this->defaultExecutableSearchPaths;
|
||||
|
||||
// Add configured processor_path on top
|
||||
$imPath = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path'];
|
||||
if ((string)$imPath !== '' && !in_array($imPath, $searchPaths)) {
|
||||
$path = $this->cleanUpPath($imPath);
|
||||
array_unshift($searchPaths, $path);
|
||||
}
|
||||
|
||||
// Add configured processor_path_lzw on top
|
||||
$imLzwSearchPath = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path_lzw'];
|
||||
if ((string)$imLzwSearchPath !== '' && !in_array($imLzwSearchPath, $searchPaths)) {
|
||||
$path = $this->cleanUpPath($imLzwSearchPath);
|
||||
array_unshift($searchPaths, $path);
|
||||
}
|
||||
|
||||
// Add additional search path from form if given
|
||||
if (isset($this->postValues['additionalSearchPath'])
|
||||
&& (string)$this->postValues['additionalSearchPath'] !== ''
|
||||
&& !in_array($this->postValues['additionalSearchPath'], $searchPaths)
|
||||
) {
|
||||
$path = $this->cleanUpPath($this->postValues['additionalSearchPath']);
|
||||
array_unshift($searchPaths, $path);
|
||||
}
|
||||
|
||||
return $searchPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidate between Windows and Unix and add trailing slash im missing
|
||||
*
|
||||
* @param string $path Given path
|
||||
* @return string Cleaned up path
|
||||
*/
|
||||
protected function cleanUpPath($path)
|
||||
{
|
||||
$path = \TYPO3\CMS\Core\Utility\GeneralUtility::fixWindowsFilePath($path);
|
||||
// Add trailing slash if missing
|
||||
if (!preg_match('/[\\/]$/', $path)) {
|
||||
$path .= '/';
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Image;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Custom preset is a fallback if no other preset fits
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class CustomPreset extends Configuration\AbstractCustomPreset implements Configuration\CustomPresetInterface
|
||||
{
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'GFX/processor_enabled' => false,
|
||||
'GFX/processor_path' => '',
|
||||
'GFX/processor_path_lzw' => '',
|
||||
'GFX/processor' => '',
|
||||
'GFX/processor_effects' => false,
|
||||
'GFX/processor_allowTemporaryMasksAsPng' => true,
|
||||
'GFX/processor_colorspace' => '',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Image;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Preset for GraphicsMagick
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class GraphicsMagickPreset extends AbstractImagePreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'GraphicsMagick';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 80;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'GFX/processor_enabled' => true,
|
||||
// processor_path and processor_path_lzw are determined and set by path lookup methods
|
||||
'GFX/processor_path' => '',
|
||||
'GFX/processor_path_lzw' => '',
|
||||
'GFX/processor' => 'GraphicsMagick',
|
||||
'GFX/processor_effects' => false,
|
||||
'GFX/processor_allowTemporaryMasksAsPng' => false,
|
||||
'GFX/processor_colorspace' => 'RGB',
|
||||
];
|
||||
|
||||
/**
|
||||
* Find executable in path, wrapper for specific ImageMagick/GraphicsMagick find methods.
|
||||
*
|
||||
* @param array $searchPaths
|
||||
* @return mixed
|
||||
*/
|
||||
protected function findExecutableInPath(array $searchPaths)
|
||||
{
|
||||
return $this->findGraphicsMagickInPaths($searchPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for GraphicsMagick executables in given paths.
|
||||
*
|
||||
* @param array $searchPaths List of paths to search for
|
||||
* @return bool TRUE if graphics magick was found in path
|
||||
*/
|
||||
protected function findGraphicsMagickInPaths(array $searchPaths)
|
||||
{
|
||||
$result = false;
|
||||
foreach ($searchPaths as $path) {
|
||||
if (Environment::isWindows()) {
|
||||
$executable = 'gm.exe';
|
||||
} else {
|
||||
$executable = 'gm';
|
||||
}
|
||||
if (@is_file($path . $executable)) {
|
||||
$command = escapeshellarg($path . $executable) . ' -version';
|
||||
$executingResult = false;
|
||||
\TYPO3\CMS\Core\Utility\CommandUtility::exec($command, $executingResult);
|
||||
// First line of exec command should contain string GraphicsMagick
|
||||
$firstResultLine = array_shift($executingResult);
|
||||
if (strpos($firstResultLine, 'GraphicsMagick') !== false) {
|
||||
$this->foundPath = $path;
|
||||
$result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Image;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Image feature detects imagemagick / graphicsmagick versions
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class ImageFeature extends Configuration\AbstractFeature implements Configuration\FeatureInterface
|
||||
{
|
||||
/**
|
||||
* @var string Name of feature
|
||||
*/
|
||||
protected $name = 'Image';
|
||||
|
||||
/**
|
||||
* @var array List of preset classes
|
||||
*/
|
||||
protected $presetRegistry = [
|
||||
\TYPO3\CMS\Install\Configuration\Image\GraphicsMagickPreset::class,
|
||||
\TYPO3\CMS\Install\Configuration\Image\ImageMagick6Preset::class,
|
||||
\TYPO3\CMS\Install\Configuration\Image\CustomPreset::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Image feature can be fed with an additional path to search for executables,
|
||||
* this getter returns the given input string (for Fluid)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAdditionalSearchPath()
|
||||
{
|
||||
$additionalPath = '';
|
||||
if (isset($this->postValues['additionalSearchPath']) && $this->postValues['additionalSearchPath'] !== '') {
|
||||
$additionalPath = $this->postValues['additionalSearchPath'];
|
||||
}
|
||||
return $additionalPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Image;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Preset for ImageMagick version 6 or higher
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class ImageMagick6Preset extends AbstractImagePreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'ImageMagick6';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 70;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'GFX/processor_enabled' => true,
|
||||
// processor_path and processor_path_lzw are determined and set by path lookup methods
|
||||
'GFX/processor_path' => '',
|
||||
'GFX/processor_path_lzw' => '',
|
||||
'GFX/processor' => 'ImageMagick',
|
||||
'GFX/processor_effects' => true,
|
||||
'GFX/processor_allowTemporaryMasksAsPng' => false,
|
||||
'GFX/processor_colorspace' => 'sRGB',
|
||||
];
|
||||
|
||||
/**
|
||||
* Find executable in path, wrapper for specific ImageMagick/GraphicsMagick find methods.
|
||||
*
|
||||
* @param array $searchPaths
|
||||
* @return mixed
|
||||
*/
|
||||
protected function findExecutableInPath(array $searchPaths)
|
||||
{
|
||||
return $this->findImageMagick6InPaths($searchPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for GraphicsMagick executables in given paths.
|
||||
*
|
||||
* @param array $searchPaths List of paths to search for
|
||||
* @return bool TRUE if graphics magick was found in path
|
||||
*/
|
||||
protected function findImageMagick6InPaths(array $searchPaths)
|
||||
{
|
||||
$result = false;
|
||||
foreach ($searchPaths as $path) {
|
||||
if (Environment::isWindows()) {
|
||||
$executable = 'identify.exe';
|
||||
|
||||
if (!@is_file($path . $executable)) {
|
||||
$executable = 'magick.exe';
|
||||
}
|
||||
} else {
|
||||
$executable = 'identify';
|
||||
}
|
||||
if (@is_file($path . $executable)) {
|
||||
$command = escapeshellarg($path . $executable) . ' -version';
|
||||
$executingResult = false;
|
||||
\TYPO3\CMS\Core\Utility\CommandUtility::exec($command, $executingResult);
|
||||
// First line of exec command should contain string GraphicsMagick
|
||||
$firstResultLine = array_shift($executingResult);
|
||||
// Example: "Version: ImageMagick 6.6.0-4 2012-05-02 Q16 http://www.imagemagick.org"
|
||||
if (strpos($firstResultLine, 'ImageMagick') !== false) {
|
||||
list(, $version) = explode('ImageMagick', $firstResultLine);
|
||||
// Example: "6.6.0-4"
|
||||
list($version) = explode(' ', trim($version));
|
||||
if (version_compare($version, '6.0.0') >= 0) {
|
||||
$this->foundPath = $path;
|
||||
$result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Mail;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Custom preset is a fallback if no other preset fits
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class CustomPreset extends Configuration\AbstractCustomPreset implements Configuration\CustomPresetInterface
|
||||
{
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'MAIL/transport' => '',
|
||||
'MAIL/transport_sendmail_command' => '',
|
||||
'MAIL/transport_smtp_server' => '',
|
||||
'MAIL/transport_smtp_encrypt' => '',
|
||||
'MAIL/transport_smtp_username' => '',
|
||||
'MAIL/transport_smtp_password' => '',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Mail;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Mail feature detects sendmail settings
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class MailFeature extends Configuration\AbstractFeature implements Configuration\FeatureInterface
|
||||
{
|
||||
/**
|
||||
* @var string Name of feature
|
||||
*/
|
||||
protected $name = 'Mail';
|
||||
|
||||
/**
|
||||
* @var array List of preset classes
|
||||
*/
|
||||
protected $presetRegistry = [
|
||||
SendmailPreset::class,
|
||||
SmtpPreset::class,
|
||||
CustomPreset::class,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Mail;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration;
|
||||
|
||||
/**
|
||||
* Sendmail path handling preset
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class SendmailPreset extends Configuration\AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'Sendmail';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 50;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'MAIL/transport' => 'sendmail',
|
||||
'MAIL/transport_sendmail_command' => '',
|
||||
'MAIL/transport_smtp_server' => '',
|
||||
'MAIL/transport_smtp_encrypt' => '',
|
||||
'MAIL/transport_smtp_username' => '',
|
||||
'MAIL/transport_smtp_password' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get configuration values to activate prefix
|
||||
*
|
||||
* @return array Configuration values needed to activate prefix
|
||||
*/
|
||||
public function getConfigurationValues()
|
||||
{
|
||||
$configurationValues = $this->configurationValues;
|
||||
$configurationValues['MAIL/transport_sendmail_command'] = $this->getSendmailPath();
|
||||
if ($this->postValues['Mail']['enable'] === 'Sendmail') {
|
||||
$configurationValues['MAIL/transport'] = 'sendmail';
|
||||
}
|
||||
return $configurationValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sendmail path if set
|
||||
*
|
||||
* @return bool TRUE if sendmail path if set
|
||||
*/
|
||||
public function isAvailable()
|
||||
{
|
||||
return !empty($this->getSendmailPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Path where executable was found
|
||||
*
|
||||
* @return string|bool Sendmail path or FALSE if not set
|
||||
*/
|
||||
public function getSendmailPath()
|
||||
{
|
||||
return ini_get('sendmail_path');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is preset is currently active on the system
|
||||
*
|
||||
* @return bool TRUE if preset is active
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
$this->configurationValues['MAIL/transport_sendmail_command'] = $this->getSendmailPath();
|
||||
return parent::isActive();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration\Mail;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration;
|
||||
|
||||
/**
|
||||
* SMTP settings handling preset
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class SmtpPreset extends Configuration\AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'Smtp';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 40;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'MAIL/transport' => 'smtp',
|
||||
'MAIL/transport_sendmail_command' => '',
|
||||
'MAIL/transport_smtp_server' => 'localhost:25',
|
||||
'MAIL/transport_smtp_encrypt' => '',
|
||||
'MAIL/transport_smtp_username' => '',
|
||||
'MAIL/transport_smtp_password' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get configuration values to activate prefix
|
||||
*
|
||||
* @return array Configuration values needed to activate prefix
|
||||
*/
|
||||
public function getConfigurationValues()
|
||||
{
|
||||
$configurationValues = $this->configurationValues;
|
||||
$keys = array_keys($configurationValues);
|
||||
foreach ($keys as $key) {
|
||||
if (!empty($this->postValues['Smtp'][$key])) {
|
||||
$configurationValues[$key] = $this->postValues['Smtp'][$key];
|
||||
}
|
||||
}
|
||||
if ($this->postValues['Mail']['enable'] === 'Smtp') {
|
||||
$configurationValues['MAIL/transport'] = 'smtp';
|
||||
}
|
||||
return $configurationValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sendmail path if set
|
||||
*
|
||||
* @return bool TRUE if sendmail path if set
|
||||
*/
|
||||
public function isAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Configuration\PasswordHashing;
|
||||
|
||||
/*
|
||||
* 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\Crypto\PasswordHashing\Argon2iPasswordHash;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Configuration\AbstractPreset;
|
||||
|
||||
/**
|
||||
* Preset for password hashing method "argon2i"
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class Argon2iPreset extends AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'Argon2i';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 70;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'BE/passwordHashing/className' => Argon2iPasswordHash::class,
|
||||
'BE/passwordHashing/options' => [],
|
||||
'FE/passwordHashing/className' => Argon2iPasswordHash::class,
|
||||
'FE/passwordHashing/options' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Find out if Argon2i is available on this system
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return GeneralUtility::makeInstance(Argon2iPasswordHash::class)->isAvailable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Configuration\PasswordHashing;
|
||||
|
||||
/*
|
||||
* 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\Crypto\PasswordHashing\BcryptPasswordHash;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Configuration\AbstractPreset;
|
||||
|
||||
/**
|
||||
* Preset for password hashing method "bcrypt"
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class BcryptPreset extends AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'Bcrypt';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 60;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'BE/passwordHashing/className' => BcryptPasswordHash::class,
|
||||
'BE/passwordHashing/options' => [],
|
||||
'FE/passwordHashing/className' => BcryptPasswordHash::class,
|
||||
'FE/passwordHashing/options' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Find out if bcrypt is available on this system
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return GeneralUtility::makeInstance(BcryptPasswordHash::class)->isAvailable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Configuration\PasswordHashing;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration\AbstractCustomPreset;
|
||||
use TYPO3\CMS\Install\Configuration\CustomPresetInterface;
|
||||
|
||||
/**
|
||||
* Preset used if custom password hashing configuration has been applied.
|
||||
* Note this custom preset does not allow manipulation via gui, this has to be done manually.
|
||||
* This preset only find out if it is active and shows the current values.
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface
|
||||
{
|
||||
/**
|
||||
* Get configuration values is used in fluid to show configuration options.
|
||||
* They are fetched from LocalConfiguration / DefaultConfiguration.
|
||||
*
|
||||
* @return array Current custom configuration values
|
||||
*/
|
||||
public function getConfigurationValues(): array
|
||||
{
|
||||
$configurationValues = [];
|
||||
$configurationValues['BE/passwordHashing/className'] =
|
||||
$this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className');
|
||||
$options = (array)$this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/options');
|
||||
foreach ($options as $optionName => $optionValue) {
|
||||
$configurationValues['BE/passwordHashing/options/' . $optionName] = $optionValue;
|
||||
}
|
||||
$configurationValues['FE/passwordHashing/className'] =
|
||||
$this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className');
|
||||
$options = (array)$this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/options');
|
||||
foreach ($options as $optionName => $optionValue) {
|
||||
$configurationValues['FE/passwordHashing/options/' . $optionName] = $optionValue;
|
||||
}
|
||||
return $configurationValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Configuration\PasswordHashing;
|
||||
|
||||
/*
|
||||
* 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\Install\Configuration\AbstractFeature;
|
||||
use TYPO3\CMS\Install\Configuration\FeatureInterface;
|
||||
|
||||
/**
|
||||
* Password hashing feature detects password hashing capabilities of the system
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class PasswordHashingFeature extends AbstractFeature implements FeatureInterface
|
||||
{
|
||||
/**
|
||||
* @var string Name of feature
|
||||
*/
|
||||
protected $name = 'PasswordHashing';
|
||||
|
||||
/**
|
||||
* @var array List of preset classes
|
||||
*/
|
||||
protected $presetRegistry = [
|
||||
Argon2iPreset::class,
|
||||
BcryptPreset::class,
|
||||
Pbkdf2Preset::class,
|
||||
PhpassPreset::class,
|
||||
CustomPreset::class,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Configuration\PasswordHashing;
|
||||
|
||||
/*
|
||||
* 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\Crypto\PasswordHashing\Pbkdf2PasswordHash;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Configuration\AbstractPreset;
|
||||
|
||||
/**
|
||||
* Preset for password hashing method "PBKDF2"
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class Pbkdf2Preset extends AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'Pbkdf2';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 50;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'BE/passwordHashing/className' => Pbkdf2PasswordHash::class,
|
||||
'BE/passwordHashing/options' => [],
|
||||
'FE/passwordHashing/className' => Pbkdf2PasswordHash::class,
|
||||
'FE/passwordHashing/options' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Find out if PBKDF2 is available on this system
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return GeneralUtility::makeInstance(Pbkdf2PasswordHash::class)->isAvailable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Configuration\PasswordHashing;
|
||||
|
||||
/*
|
||||
* 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\Crypto\PasswordHashing\PhpassPasswordHash;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Configuration\AbstractPreset;
|
||||
|
||||
/**
|
||||
* Preset for password hashing method "phpass"
|
||||
* @internal only to be used within EXT:install
|
||||
*/
|
||||
class PhpassPreset extends AbstractPreset
|
||||
{
|
||||
/**
|
||||
* @var string Name of preset
|
||||
*/
|
||||
protected $name = 'Phpass';
|
||||
|
||||
/**
|
||||
* @var int Priority of preset
|
||||
*/
|
||||
protected $priority = 40;
|
||||
|
||||
/**
|
||||
* @var array Configuration values handled by this preset
|
||||
*/
|
||||
protected $configurationValues = [
|
||||
'BE/passwordHashing/className' => PhpassPasswordHash::class,
|
||||
'BE/passwordHashing/options' => [],
|
||||
'FE/passwordHashing/className' => PhpassPasswordHash::class,
|
||||
'FE/passwordHashing/options' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Find out if Phpass is available on this system
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return GeneralUtility::makeInstance(PhpassPasswordHash::class)->isAvailable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Configuration;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Preset interface
|
||||
*
|
||||
* A preset is a class for handling a specific configuration
|
||||
* set of a feature.
|
||||
*/
|
||||
interface PresetInterface
|
||||
{
|
||||
/**
|
||||
* Set POST values
|
||||
*
|
||||
* @param array $postValues Post values of feature
|
||||
* @return mixed
|
||||
*/
|
||||
public function setPostValues(array $postValues);
|
||||
|
||||
/**
|
||||
* Check if preset is available on the system
|
||||
*
|
||||
* @return bool TRUE if preset is available
|
||||
*/
|
||||
public function isAvailable();
|
||||
|
||||
/**
|
||||
* Wrapper for isAvailable, used in fluid
|
||||
*
|
||||
* @return bool TRUE if preset is available
|
||||
*/
|
||||
public function getIsAvailable();
|
||||
|
||||
/**
|
||||
* Check is preset is currently active on the system
|
||||
*
|
||||
* @return bool TRUE if preset is active
|
||||
*/
|
||||
public function isActive();
|
||||
|
||||
/**
|
||||
* Wrapper for isActive, used in fluid
|
||||
*
|
||||
* @return bool TRUE if preset is active
|
||||
*/
|
||||
public function getIsActive();
|
||||
|
||||
/**
|
||||
* Get name of preset
|
||||
*
|
||||
* @return string Name
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Get priority of preset
|
||||
*
|
||||
* @return int Priority, usually between 0 and 100
|
||||
*/
|
||||
public function getPriority();
|
||||
|
||||
/**
|
||||
* Get configuration values to activate prefix
|
||||
*
|
||||
* @return array Configuration values needed to activate prefix
|
||||
*/
|
||||
public function getConfigurationValues();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\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 Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
|
||||
/**
|
||||
* Controller abstract for shared parts of the install tool
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class AbstractController
|
||||
{
|
||||
/**
|
||||
* Helper method to initialize a standalone view instance.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param string $templatePath
|
||||
* @return StandaloneView
|
||||
* @internal param string $template
|
||||
*/
|
||||
protected function initializeStandaloneView(ServerRequestInterface $request, string $templatePath): StandaloneView
|
||||
{
|
||||
$viewRootPath = GeneralUtility::getFileAbsFileName('EXT:install/Resources/Private/');
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->getRequest()->setControllerExtensionName('Install');
|
||||
$view->setTemplatePathAndFilename($viewRootPath . 'Templates/' . $templatePath);
|
||||
$view->setLayoutRootPaths([$viewRootPath . 'Layouts/']);
|
||||
$view->setPartialRootPaths([$viewRootPath . 'Partials/']);
|
||||
$view->assignMultiple([
|
||||
'controller' => $request->getQueryParams()['install']['controller'] ?? 'maintenance',
|
||||
'context' => $request->getQueryParams()['install']['context'] ?? '',
|
||||
'composerMode' => Environment::isComposerMode(),
|
||||
]);
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some actions like the database analyzer and the upgrade wizards need additional
|
||||
* bootstrap actions performed.
|
||||
*
|
||||
* Those actions can potentially fatal if some old extension is loaded that triggers
|
||||
* a fatal in ext_localconf or ext_tables code! Use only if really needed.
|
||||
*/
|
||||
protected function loadExtLocalconfDatabaseAndExtTables()
|
||||
{
|
||||
\TYPO3\CMS\Core\Core\Bootstrap::loadTypo3LoadedExtAndExtLocalconf(false);
|
||||
\TYPO3\CMS\Core\Core\Bootstrap::unsetReservedGlobalVariables();
|
||||
\TYPO3\CMS\Core\Core\Bootstrap::loadBaseTca(false);
|
||||
\TYPO3\CMS\Core\Core\Bootstrap::loadExtTables(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\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 Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Core\Authentication\AbstractAuthenticationService;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Http\Uri;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
use TYPO3\CMS\Install\Service\SessionService;
|
||||
use TYPO3\CMS\Rsaauth\RsaEncryptionEncoder;
|
||||
|
||||
/**
|
||||
* Backend module controller to the Install Tool. Sets an Install Tool session
|
||||
* marked as "initialized by a valid system administrator backend user" and
|
||||
* redirects to the Install Tool entry point.
|
||||
*
|
||||
* This is a classic backend module that does not interfere with other code
|
||||
* within the Install Tool, it can be seen as a facade around Install Tool just
|
||||
* to embed the Install Tool in backend.
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class BackendModuleController
|
||||
{
|
||||
private const FLAG_CONFIRMATION_REQUEST = 1;
|
||||
protected const FLAG_INSTALL_TOOL_PASSWORD = 2;
|
||||
private const ALLOWED_ACTIONS = ['maintenance', 'settings', 'upgrade', 'environment'];
|
||||
|
||||
/**
|
||||
* @var SessionService
|
||||
*/
|
||||
protected $sessionService;
|
||||
|
||||
/**
|
||||
* @var UriBuilder
|
||||
*/
|
||||
protected $uriBuilder;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows and handles backend user session confirmation ("sudo mode") for
|
||||
* accessing a particular Install Tool controller (as given in `$targetController`).
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function backendUserConfirmationAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$flags = (int)($request->getQueryParams()['flags'] ?? 0);
|
||||
$targetController = (string)($request->getQueryParams()['targetController'] ?? '');
|
||||
$targetHash = (string)($request->getQueryParams()['targetHash'] ?? '');
|
||||
$expectedTargetHash = GeneralUtility::hmac($targetController, BackendModuleController::class);
|
||||
$flagInstallToolPassword = (bool)($flags & self::FLAG_INSTALL_TOOL_PASSWORD);
|
||||
$flagInvalidPassword = false;
|
||||
|
||||
if (!in_array($targetController, self::ALLOWED_ACTIONS, true)
|
||||
|| !hash_equals($expectedTargetHash, $targetHash)) {
|
||||
return new HtmlResponse('', 403);
|
||||
}
|
||||
if ($flags & self::FLAG_CONFIRMATION_REQUEST) {
|
||||
if ($flagInstallToolPassword && $this->verifyInstallToolPassword($request)) {
|
||||
return $this->setAuthorizedAndRedirect($targetController);
|
||||
}
|
||||
if (!$flagInstallToolPassword && $this->verifyBackendUserPassword($request)) {
|
||||
return $this->setAuthorizedAndRedirect($targetController);
|
||||
}
|
||||
$flagInvalidPassword = true;
|
||||
}
|
||||
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->getTemplatePaths()->setTemplatePathAndFilename(
|
||||
ExtensionManagementUtility::extPath(
|
||||
'install',
|
||||
'Resources/Private/Templates/BackendModule/BackendUserConfirmation.html'
|
||||
)
|
||||
);
|
||||
$view->assignMultiple([
|
||||
'flagInvalidPassword' => $flagInvalidPassword,
|
||||
'flagInstallToolPassword' => $flagInstallToolPassword,
|
||||
'languageFileReference' => 'LLL:EXT:install/Resources/Private/Language/BackendModule.xlf',
|
||||
'passwordModeUri' => $this->getBackendUserConfirmationUri([
|
||||
'targetController' => $targetController,
|
||||
'targetHash' => $targetHash,
|
||||
// current flags, unset FLAG_CONFIRMATION_REQUEST, toggle FLAG_INSTALL_TOOL_PASSWORD
|
||||
'flags' => $flags & ~self::FLAG_CONFIRMATION_REQUEST ^ self::FLAG_INSTALL_TOOL_PASSWORD,
|
||||
]),
|
||||
'verifyUri' => $this->getBackendUserConfirmationUri([
|
||||
'targetController' => $targetController,
|
||||
'targetHash' => $targetHash,
|
||||
// current flags, add FLAG_CONFIRMATION_REQUEST
|
||||
'flags' => $flags | self::FLAG_CONFIRMATION_REQUEST,
|
||||
]),
|
||||
'cancelUri' => '',
|
||||
]);
|
||||
$this->applyRsaAuthModules();
|
||||
return new HtmlResponse($view->render());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize session and redirect to "maintenance"
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function maintenanceAction(): ResponseInterface
|
||||
{
|
||||
return $this->getBackendUserConfirmationRedirect('maintenance')
|
||||
?? $this->setAuthorizedAndRedirect('maintenance');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize session and redirect to "settings"
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function settingsAction(): ResponseInterface
|
||||
{
|
||||
return $this->getBackendUserConfirmationRedirect('settings')
|
||||
?? $this->setAuthorizedAndRedirect('settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize session and redirect to "upgrade"
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function upgradeAction(): ResponseInterface
|
||||
{
|
||||
return $this->getBackendUserConfirmationRedirect('upgrade')
|
||||
?? $this->setAuthorizedAndRedirect('upgrade');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize session and redirect to "environment"
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function environmentAction(): ResponseInterface
|
||||
{
|
||||
return $this->getBackendUserConfirmationRedirect('environment')
|
||||
?? $this->setAuthorizedAndRedirect('environment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates redirect response to backend user confirmation (if required).
|
||||
*
|
||||
* @param string $targetController
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
protected function getBackendUserConfirmationRedirect(string $targetController): ?ResponseInterface
|
||||
{
|
||||
if ($this->getSessionService()->isAuthorizedBackendUserSession()) {
|
||||
return null;
|
||||
}
|
||||
if (Environment::getContext()->isDevelopment()) {
|
||||
return null;
|
||||
}
|
||||
$redirectUri = $this->getBackendUserConfirmationUri([
|
||||
'targetController' => $targetController,
|
||||
'targetHash' => GeneralUtility::hmac($targetController, BackendModuleController::class),
|
||||
]);
|
||||
return new RedirectResponse((string)$redirectUri, 403);
|
||||
}
|
||||
|
||||
protected function getBackendUserConfirmationUri(array $parameters): Uri
|
||||
{
|
||||
return $this->uriBuilder->buildUriFromRoute(
|
||||
'install.backend-user-confirmation',
|
||||
$parameters
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts / updates the session and redirects to the Install Tool
|
||||
* with given action.
|
||||
*
|
||||
* @param $controller
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function setAuthorizedAndRedirect(string $controller): ResponseInterface
|
||||
{
|
||||
$this->getSessionService()->setAuthorizedBackendSession();
|
||||
$redirectLocation = 'install.php?install[controller]=' . $controller . '&install[context]=backend';
|
||||
return new RedirectResponse($redirectLocation, 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that provided password matches Install Tool password.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function verifyInstallToolPassword(ServerRequestInterface $request): bool
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$password = $parsedBody['confirmationPassword'] ?? null;
|
||||
$installToolPassword = $GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'] ?? null;
|
||||
if (!is_string($password) || empty($installToolPassword)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$hashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
|
||||
$hashInstance = $hashFactory->get($installToolPassword, 'BE');
|
||||
return $hashInstance->checkPassword($password, $installToolPassword);
|
||||
} catch (InvalidPasswordHashException $exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that provided password is actually correct for current backend user
|
||||
* by stepping through authentication chain in `$GLOBALS['BE_USER]`.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function verifyBackendUserPassword(ServerRequestInterface $request): bool
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$password = empty($parsedBody['confirmationPasswordInternal'])
|
||||
? ($parsedBody['confirmationPassword'] ?? null) // default field
|
||||
: $parsedBody['confirmationPasswordInternal']; // filled e.g. by `ext:rsaauth`
|
||||
if (!is_string($password)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// clone current backend user object to avoid
|
||||
// possible side effects for the real instance
|
||||
$backendUser = clone $this->getBackendUser();
|
||||
$loginData = [
|
||||
'status' => 'sudo-mode',
|
||||
'origin' => BackendModuleController::class,
|
||||
'uname' => $backendUser->user['username'],
|
||||
'uident' => $password,
|
||||
];
|
||||
// currently there is no dedicated API to perform authentication
|
||||
// that's why this process partially has to be simulated here
|
||||
$loginData = $backendUser->processLoginData($loginData);
|
||||
$authInfo = $backendUser->getAuthInfoArray();
|
||||
|
||||
$authenticated = false;
|
||||
/** @var AbstractAuthenticationService $service or any other service (sic!) */
|
||||
foreach ($this->getAuthServices($backendUser, $loginData, $authInfo) as $service) {
|
||||
$ret = (int)$service->authUser($backendUser->user);
|
||||
if ($ret <= 0) {
|
||||
return false;
|
||||
}
|
||||
if ($ret >= 200) {
|
||||
return true;
|
||||
}
|
||||
if ($ret >= 100) {
|
||||
continue;
|
||||
}
|
||||
$authenticated = true;
|
||||
continue;
|
||||
}
|
||||
return $authenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes authentication services to be used in a foreach loop
|
||||
*
|
||||
* @param BackendUserAuthentication $backendUser
|
||||
* @param array $loginData
|
||||
* @param array $authInfo
|
||||
* @return \Generator<int, object>
|
||||
*/
|
||||
protected function getAuthServices(BackendUserAuthentication $backendUser, array $loginData, array $authInfo): \Generator
|
||||
{
|
||||
$serviceChain = [];
|
||||
$subType = 'authUserBE';
|
||||
while ($service = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain)) {
|
||||
$serviceChain[] = $service->getServiceKey();
|
||||
if (!is_object($service)) {
|
||||
break;
|
||||
}
|
||||
$service->initAuth($subType, $loginData, $authInfo, $backendUser);
|
||||
yield $service;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Tool modified sessions meta-data (handler, storage, name) which
|
||||
* conflicts with existing session that for instance is used for ext:rsaauth.
|
||||
*
|
||||
* @return SessionService
|
||||
*/
|
||||
protected function getSessionService(): SessionService
|
||||
{
|
||||
if ($this->sessionService === null) {
|
||||
$this->sessionService = new SessionService();
|
||||
$this->sessionService->startSession();
|
||||
}
|
||||
return $this->sessionService;
|
||||
}
|
||||
|
||||
protected function applyRsaAuthModules()
|
||||
{
|
||||
if (!ExtensionManagementUtility::isLoaded('rsaauth')) {
|
||||
return;
|
||||
}
|
||||
$rsaEncryptionEncoder = GeneralUtility::makeInstance(RsaEncryptionEncoder::class);
|
||||
$rsaEncryptionEncoder->enableRsaEncryption(true);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\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 Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconRegistry;
|
||||
use TYPO3\CMS\Core\Type\Icon\IconState;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Controller for icon handling
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class IconController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var IconRegistry
|
||||
*/
|
||||
protected $iconRegistry;
|
||||
|
||||
/**
|
||||
* @var IconFactory
|
||||
*/
|
||||
protected $iconFactory;
|
||||
|
||||
/**
|
||||
* Set up dependencies
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
|
||||
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
* @internal
|
||||
*/
|
||||
public function getCacheIdentifierAction(): ResponseInterface
|
||||
{
|
||||
return new HtmlResponse($this->iconRegistry->getCacheIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @internal
|
||||
*/
|
||||
public function getIconAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$queryParams = $request->getQueryParams();
|
||||
$requestedIcon = json_decode($parsedBody['icon'] ?? $queryParams['icon'], true);
|
||||
|
||||
list($identifier, $size, $overlayIdentifier, $iconState, $alternativeMarkupIdentifier) = $requestedIcon;
|
||||
|
||||
if (empty($overlayIdentifier)) {
|
||||
$overlayIdentifier = null;
|
||||
}
|
||||
|
||||
$iconState = IconState::cast($iconState);
|
||||
$icon = $this->iconFactory->getIcon($identifier, $size, $overlayIdentifier, $iconState);
|
||||
|
||||
return new HtmlResponse($icon->render($alternativeMarkupIdentifier));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\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 Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
|
||||
use TYPO3\CMS\Install\Service\Exception\ConfigurationChangedException;
|
||||
use TYPO3\CMS\Install\Service\SilentConfigurationUpgradeService;
|
||||
|
||||
/**
|
||||
* Layout controller
|
||||
*
|
||||
* Renders a first "load the Javascript in <head>" view, and the
|
||||
* main layout of the install tool in second action.
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class LayoutController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* The init action renders an HTML response with HTML view having <head> section
|
||||
* containing resources to main .js routing.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function initAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Layout/Init.html');
|
||||
$view->assignMultiple([
|
||||
// time is used as cache bust for js and css resources
|
||||
'time' => time(),
|
||||
'siteName' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
|
||||
]);
|
||||
return new HtmlResponse(
|
||||
$view->render(),
|
||||
200,
|
||||
[
|
||||
'Cache-Control' => 'no-cache, must-revalidate',
|
||||
'Pragma' => 'no-cache'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a json response with the main HTML layout body: Toolbar, main menu and
|
||||
* doc header in standalone, doc header only in backend context. Silent updaters
|
||||
* are executed before this main view is loaded.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function mainLayoutAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Layout/MainLayout.html');
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute silent configuration update. May be called multiple times until success = true is returned.
|
||||
*
|
||||
* @return ResponseInterface success = true if no change has been done
|
||||
*/
|
||||
public function executeSilentConfigurationUpdateAction(): ResponseInterface
|
||||
{
|
||||
$silentUpdate = new SilentConfigurationUpgradeService();
|
||||
$success = true;
|
||||
try {
|
||||
$silentUpdate->execute();
|
||||
} catch (ConfigurationChangedException $e) {
|
||||
$success = false;
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => $success,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy ajax call. This silent updater takes care that all extensions configured in LocalConfiguration
|
||||
* EXT/extConf serialized array are "upmerged" to arrays within EXTENSIONS if this extension does not
|
||||
* exist in EXTENSIONS yet.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @deprecated since TYPO3 v9, will be removed with TYPO3 v10.0
|
||||
*/
|
||||
public function executeSilentLegacyExtConfExtensionConfigurationUpdateAction(): ResponseInterface
|
||||
{
|
||||
$configurationManager = new ConfigurationManager();
|
||||
try {
|
||||
$oldExtConfSettings = $configurationManager->getConfigurationValueByPath('EXT/extConf');
|
||||
} catch (MissingArrayPathException $e) {
|
||||
// The old 'extConf' array may not exist anymore, set to empty array if so.
|
||||
$oldExtConfSettings = [];
|
||||
}
|
||||
try {
|
||||
$newExtensionSettings = $configurationManager->getConfigurationValueByPath('EXTENSIONS');
|
||||
} catch (MissingArrayPathException $e) {
|
||||
// New 'EXTENSIONS' array may not exist yet, for instance if just upgrading to v9
|
||||
$newExtensionSettings = [];
|
||||
}
|
||||
foreach ($oldExtConfSettings as $extensionName => $extensionSettings) {
|
||||
if (!array_key_exists($extensionName, $newExtensionSettings)) {
|
||||
$unserializedConfiguration = unserialize($extensionSettings, ['allowed_classes' => false]);
|
||||
if (is_array($unserializedConfiguration)) {
|
||||
$newExtensionSettings = $this->removeDotsFromArrayKeysRecursive($unserializedConfiguration);
|
||||
$configurationManager->setLocalConfigurationValueByPath('EXTENSIONS/' . $extensionName, $newExtensionSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize TYPO3_CONF_VARS['EXTENSIONS'] with possibly new defaults from extensions
|
||||
* ext_conf_template.txt files. This make LocalConfiguration the only source of truth for
|
||||
* extension configuration and it is always up to date, also if an extension has been
|
||||
* updated.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function executeSilentExtensionConfigurationSynchronizationAction(): ResponseInterface
|
||||
{
|
||||
$extensionConfiguration = new ExtensionConfiguration();
|
||||
$extensionConfiguration->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions();
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for executeSilentLegacyExtConfExtensionConfigurationUpdateAction(). Old EXT/extConf
|
||||
* settings have dots at the end of array keys if nested arrays were used. The new configuration does
|
||||
* not use this funny nested representation anymore. The method removes all dots at the end of given
|
||||
* array keys recursive to do this transition.
|
||||
*
|
||||
* @param array $settings
|
||||
* @return array New settings
|
||||
* @deprecated since TYPO3 v9, will be removed with TYPO3 v10.0 along with executeSilentLegacyExtConfExtensionConfigurationUpdateAction()
|
||||
*/
|
||||
private function removeDotsFromArrayKeysRecursive(array $settings): array
|
||||
{
|
||||
$settingsWithoutDots = [];
|
||||
foreach ($settings as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$settingsWithoutDots[rtrim($key, '.')] = $this->removeDotsFromArrayKeysRecursive($value);
|
||||
} else {
|
||||
$settingsWithoutDots[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $settingsWithoutDots;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\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 Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Install\Service\EnableFileService;
|
||||
|
||||
/**
|
||||
* Login controller
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class LoginController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* Render the "Create an "enable install tool file" action
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function showEnableInstallToolFileAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Login/ShowEnableInstallToolFile.html');
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render login view
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function showLoginAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view = $this->initializeStandaloneView($request, 'Login/ShowLogin.html');
|
||||
$view->assignMultiple([
|
||||
'siteName' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
|
||||
'loginToken' => $formProtection->generateToken('installTool', 'login'),
|
||||
'installToolEnableFilePermanent' => EnableFileService::isInstallToolEnableFilePermanent(),
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,770 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\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 Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\Core\ClassLoadingInformation;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Schema\Exception\StatementException;
|
||||
use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
|
||||
use TYPO3\CMS\Core\Database\Schema\SqlReader;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Localization\Locales;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Service\OpcodeCacheService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Service\ClearCacheService;
|
||||
use TYPO3\CMS\Install\Service\ClearTableService;
|
||||
use TYPO3\CMS\Install\Service\LanguagePackService;
|
||||
use TYPO3\CMS\Install\Service\Typo3tempFileService;
|
||||
|
||||
/**
|
||||
* Maintenance controller
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class MaintenanceController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* Main "show the cards" view
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function cardsAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Maintenance/Cards.html');
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache framework and opcode caches
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function cacheClearAllAction(): ResponseInterface
|
||||
{
|
||||
GeneralUtility::makeInstance(ClearCacheService::class)->clearAll();
|
||||
GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
|
||||
$messageQueue = (new FlashMessageQueue('install'))->enqueue(
|
||||
new FlashMessage('Successfully cleared all caches and all available opcode caches.', 'Caches cleared')
|
||||
);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear typo3temp files statistics action
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function clearTypo3tempFilesStatsAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->loadExtLocalconfDatabaseAndExtTables();
|
||||
$view = $this->initializeStandaloneView($request, 'Maintenance/ClearTypo3tempFiles.html');
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view->assignMultiple([
|
||||
'clearTypo3tempFilesToken' => $formProtection->generateToken('installTool', 'clearTypo3tempFiles'),
|
||||
]);
|
||||
return new JsonResponse(
|
||||
[
|
||||
'success' => true,
|
||||
'stats' => (new Typo3tempFileService())->getDirectoryStatistics(),
|
||||
'html' => $view->render(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear typo3temp/assets or FAL processed Files
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function clearTypo3tempFilesAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->loadExtLocalconfDatabaseAndExtTables();
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
$typo3tempFileService = new Typo3tempFileService();
|
||||
$folder = $request->getParsedBody()['install']['folder'];
|
||||
// storageUid is an optional post param if FAL storages should be cleaned
|
||||
$storageUid = $request->getParsedBody()['install']['storageUid'] ?? null;
|
||||
if ($storageUid === null) {
|
||||
$typo3tempFileService->clearAssetsFolder($folder);
|
||||
$messageQueue->enqueue(new FlashMessage('Cleared files in "' . $folder . '" folder'));
|
||||
} else {
|
||||
$storageUid = (int)$storageUid;
|
||||
$failedDeletions = $typo3tempFileService->clearProcessedFiles($storageUid);
|
||||
if ($failedDeletions) {
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'Failed to delete ' . $failedDeletions . ' processed files. See TYPO3 log (by default typo3temp/var/log/typo3_*.log)',
|
||||
'',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$messageQueue->enqueue(new FlashMessage('Cleared processed files'));
|
||||
}
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump autoload information
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function dumpAutoloadAction(): ResponseInterface
|
||||
{
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
if (Environment::isComposerMode()) {
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'Skipped generating additional class loading information in composer mode.',
|
||||
'',
|
||||
FlashMessage::NOTICE
|
||||
));
|
||||
} else {
|
||||
ClassLoadingInformation::dumpClassLoadingInformation();
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'Successfully dumped class loading information for extensions.'
|
||||
));
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get main database analyzer modal HTML
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function databaseAnalyzerAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Maintenance/DatabaseAnalyzer.html');
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view->assignMultiple([
|
||||
'databaseAnalyzerExecuteToken' => $formProtection->generateToken('installTool', 'databaseAnalyzerExecute'),
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze current database situation
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function databaseAnalyzerAnalyzeAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->loadExtLocalconfDatabaseAndExtTables();
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
$suggestions = [];
|
||||
try {
|
||||
$sqlReader = GeneralUtility::makeInstance(SqlReader::class);
|
||||
$sqlStatements = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
|
||||
$schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
|
||||
$addCreateChange = $schemaMigrationService->getUpdateSuggestions($sqlStatements);
|
||||
|
||||
// Aggregate the per-connection statements into one flat array
|
||||
$addCreateChange = array_merge_recursive(...array_values($addCreateChange));
|
||||
if (!empty($addCreateChange['create_table'])) {
|
||||
$suggestion = [
|
||||
'key' => 'addTable',
|
||||
'label' => 'Add tables',
|
||||
'enabled' => true,
|
||||
'children' => [],
|
||||
];
|
||||
foreach ($addCreateChange['create_table'] as $hash => $statement) {
|
||||
$suggestion['children'][] = [
|
||||
'hash' => $hash,
|
||||
'statement' => $statement,
|
||||
];
|
||||
}
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
if (!empty($addCreateChange['add'])) {
|
||||
$suggestion = [
|
||||
'key' => 'addField',
|
||||
'label' => 'Add fields to tables',
|
||||
'enabled' => true,
|
||||
'children' => [],
|
||||
];
|
||||
foreach ($addCreateChange['add'] as $hash => $statement) {
|
||||
$suggestion['children'][] = [
|
||||
'hash' => $hash,
|
||||
'statement' => $statement,
|
||||
];
|
||||
}
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
if (!empty($addCreateChange['change'])) {
|
||||
$suggestion = [
|
||||
'key' => 'change',
|
||||
'label' => 'Change fields',
|
||||
'enabled' => false,
|
||||
'children' => [],
|
||||
];
|
||||
foreach ($addCreateChange['change'] as $hash => $statement) {
|
||||
$child = [
|
||||
'hash' => $hash,
|
||||
'statement' => $statement,
|
||||
];
|
||||
if (isset($addCreateChange['change_currentValue'][$hash])) {
|
||||
$child['current'] = $addCreateChange['change_currentValue'][$hash];
|
||||
}
|
||||
$suggestion['children'][] = $child;
|
||||
}
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
|
||||
// Difference from current to expected
|
||||
$dropRename = $schemaMigrationService->getUpdateSuggestions($sqlStatements, true);
|
||||
|
||||
// Aggregate the per-connection statements into one flat array
|
||||
$dropRename = array_merge_recursive(...array_values($dropRename));
|
||||
if (!empty($dropRename['change_table'])) {
|
||||
$suggestion = [
|
||||
'key' => 'renameTableToUnused',
|
||||
'label' => 'Remove tables (rename with prefix)',
|
||||
'enabled' => false,
|
||||
'children' => [],
|
||||
];
|
||||
foreach ($dropRename['change_table'] as $hash => $statement) {
|
||||
$child = [
|
||||
'hash' => $hash,
|
||||
'statement' => $statement,
|
||||
];
|
||||
if (!empty($dropRename['tables_count'][$hash])) {
|
||||
$child['rowCount'] = $dropRename['tables_count'][$hash];
|
||||
}
|
||||
$suggestion['children'][] = $child;
|
||||
}
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
if (!empty($dropRename['change'])) {
|
||||
$suggestion = [
|
||||
'key' => 'renameTableFieldToUnused',
|
||||
'label' => 'Remove unused fields (rename with prefix)',
|
||||
'enabled' => false,
|
||||
'children' => [],
|
||||
];
|
||||
foreach ($dropRename['change'] as $hash => $statement) {
|
||||
$suggestion['children'][] = [
|
||||
'hash' => $hash,
|
||||
'statement' => $statement,
|
||||
];
|
||||
}
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
if (!empty($dropRename['drop'])) {
|
||||
$suggestion = [
|
||||
'key' => 'deleteField',
|
||||
'label' => 'Drop fields (really!)',
|
||||
'enabled' => false,
|
||||
'children' => [],
|
||||
];
|
||||
foreach ($dropRename['drop'] as $hash => $statement) {
|
||||
$suggestion['children'][] = [
|
||||
'hash' => $hash,
|
||||
'statement' => $statement,
|
||||
];
|
||||
}
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
if (!empty($dropRename['drop_table'])) {
|
||||
$suggestion = [
|
||||
'key' => 'deleteTable',
|
||||
'label' => 'Drop tables (really!)',
|
||||
'enabled' => false,
|
||||
'children' => [],
|
||||
];
|
||||
foreach ($dropRename['drop_table'] as $hash => $statement) {
|
||||
$child = [
|
||||
'hash' => $hash,
|
||||
'statement' => $statement,
|
||||
];
|
||||
if (!empty($dropRename['tables_count'][$hash])) {
|
||||
$child['rowCount'] = $dropRename['tables_count'][$hash];
|
||||
}
|
||||
$suggestion['children'][] = $child;
|
||||
}
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
} catch (StatementException $e) {
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
$e->getMessage(),
|
||||
'Database analysis failed',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue,
|
||||
'suggestions' => $suggestions,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply selected database changes
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function databaseAnalyzerExecuteAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->loadExtLocalconfDatabaseAndExtTables();
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
$selectedHashes = $request->getParsedBody()['install']['hashes'] ?? [];
|
||||
if (empty($selectedHashes)) {
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'No database changes selected',
|
||||
FlashMessage::WARNING
|
||||
));
|
||||
} else {
|
||||
$sqlReader = GeneralUtility::makeInstance(SqlReader::class);
|
||||
$sqlStatements = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
|
||||
$schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
|
||||
$statementHashesToPerform = array_flip($selectedHashes);
|
||||
$results = $schemaMigrationService->migrate($sqlStatements, $statementHashesToPerform);
|
||||
// Create error flash messages if any
|
||||
foreach ($results as $errorMessage) {
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'Error: ' . $errorMessage,
|
||||
'Database update failed',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
}
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'Executed database updates',
|
||||
'Executed database updates'
|
||||
));
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear table overview statistics action
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function clearTablesStatsAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Maintenance/ClearTables.html');
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view->assignMultiple([
|
||||
'clearTablesClearToken' => $formProtection->generateToken('installTool', 'clearTablesClear'),
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'stats' => (new ClearTableService())->getTableStatistics(),
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate a specific table
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function clearTablesClearAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$table = $request->getParsedBody()['install']['table'];
|
||||
if (empty($table)) {
|
||||
throw new \RuntimeException(
|
||||
'No table name given',
|
||||
1501944076
|
||||
);
|
||||
}
|
||||
(new ClearTableService())->clearSelectedTable($table);
|
||||
$messageQueue = (new FlashMessageQueue('install'))->enqueue(
|
||||
new FlashMessage('Cleared table')
|
||||
);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Create Admin Get Data action
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function createAdminGetDataAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Maintenance/CreateAdmin.html');
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view->assignMultiple([
|
||||
'createAdminToken' => $formProtection->generateToken('installTool', 'createAdmin'),
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a backend administrator from given username and password
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function createAdminAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$username = preg_replace('/\\s/i', '', $request->getParsedBody()['install']['userName']);
|
||||
$password = $request->getParsedBody()['install']['userPassword'];
|
||||
$passwordCheck = $request->getParsedBody()['install']['userPasswordCheck'];
|
||||
$isSystemMaintainer = ((bool)$request->getParsedBody()['install']['userSystemMaintainer'] == '1') ? true : false;
|
||||
|
||||
$messages = new FlashMessageQueue('install');
|
||||
|
||||
if (strlen($username) < 1) {
|
||||
$messages->enqueue(new FlashMessage(
|
||||
'No valid username given.',
|
||||
'Administrator user not created',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} elseif ($password !== $passwordCheck) {
|
||||
$messages->enqueue(new FlashMessage(
|
||||
'Passwords do not match.',
|
||||
'Administrator user not created',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} elseif (strlen($password) < 8) {
|
||||
$messages->enqueue(new FlashMessage(
|
||||
'Password must be at least eight characters long.',
|
||||
'Administrator user not created',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$userExists = $connectionPool->getConnectionForTable('be_users')
|
||||
->count(
|
||||
'uid',
|
||||
'be_users',
|
||||
['username' => $username]
|
||||
);
|
||||
if ($userExists) {
|
||||
$messages->enqueue(new FlashMessage(
|
||||
'A user with username "' . $username . '" exists already.',
|
||||
'Administrator user not created',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
|
||||
$hashedPassword = $hashInstance->getHashedPassword($password);
|
||||
$adminUserFields = [
|
||||
'username' => $username,
|
||||
'password' => $hashedPassword,
|
||||
'admin' => 1,
|
||||
'tstamp' => $GLOBALS['EXEC_TIME'],
|
||||
'crdate' => $GLOBALS['EXEC_TIME']
|
||||
];
|
||||
$connectionPool->getConnectionForTable('be_users')->insert('be_users', $adminUserFields);
|
||||
|
||||
if ($isSystemMaintainer) {
|
||||
|
||||
// Get the new admin user uid juste created
|
||||
$newAdminUserUid = (int)$connectionPool->getConnectionForTable('be_users')->lastInsertId('be_users');
|
||||
|
||||
// Get the list of the existing systemMaintainer
|
||||
$existingSystemMaintainersList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? [];
|
||||
|
||||
// Add the new admin user to the existing systemMaintainer list
|
||||
$newSystemMaintainersList = $existingSystemMaintainersList;
|
||||
$newSystemMaintainersList[] = $newAdminUserUid;
|
||||
|
||||
// Update the LocalConfiguration.php file with the new list
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$configurationManager->setLocalConfigurationValuesByPathValuePairs(
|
||||
['SYS/systemMaintainers' => $newSystemMaintainersList]
|
||||
);
|
||||
}
|
||||
|
||||
$messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Administrator created with username "' . $username . '".'
|
||||
));
|
||||
}
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messages,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry action of language packs module gets
|
||||
* * list of available languages with details like active or not and last update
|
||||
* * list of loaded extensions
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function languagePacksGetDataAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Maintenance/LanguagePacks.html');
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view->assignMultiple([
|
||||
'languagePacksActivateLanguageToken' => $formProtection->generateToken('installTool', 'languagePacksActivateLanguage'),
|
||||
'languagePacksDeactivateLanguageToken' => $formProtection->generateToken('installTool', 'languagePacksDeactivateLanguage'),
|
||||
'languagePacksUpdatePackToken' => $formProtection->generateToken('installTool', 'languagePacksUpdatePack'),
|
||||
'languagePacksUpdateIsoTimesToken' => $formProtection->generateToken('installTool', 'languagePacksUpdateIsoTimes'),
|
||||
]);
|
||||
// This action needs TYPO3_CONF_VARS for full GeneralUtility::getUrl() config
|
||||
$this->loadExtLocalconfDatabaseAndExtTables();
|
||||
$languagePacksService = GeneralUtility::makeInstance(LanguagePackService::class);
|
||||
$languagePacksService->updateMirrorBaseUrl();
|
||||
$extensions = $languagePacksService->getExtensionLanguagePackDetails();
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'languages' => $languagePacksService->getLanguageDetails(),
|
||||
'extensions' => $extensions,
|
||||
'activeLanguages' => $languagePacksService->getActiveLanguages(),
|
||||
'activeExtensions' => array_column($extensions, 'key'),
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a language and any possible dependency it may have
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function languagePacksActivateLanguageAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
$languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
|
||||
$locales = GeneralUtility::makeInstance(Locales::class);
|
||||
$availableLanguages = $languagePackService->getAvailableLanguages();
|
||||
$activeLanguages = $languagePackService->getActiveLanguages();
|
||||
$iso = $request->getParsedBody()['install']['iso'];
|
||||
$activateArray = [];
|
||||
foreach ($availableLanguages as $availableIso => $name) {
|
||||
if ($availableIso === $iso && !in_array($availableIso, $activeLanguages, true)) {
|
||||
$activateArray[] = $iso;
|
||||
$dependencies = $locales->getLocaleDependencies($availableIso);
|
||||
if (!empty($dependencies)) {
|
||||
foreach ($dependencies as $dependency) {
|
||||
if (!in_array($dependency, $activeLanguages, true)) {
|
||||
$activateArray[] = $dependency;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($activateArray)) {
|
||||
$activeLanguages = array_merge($activeLanguages, $activateArray);
|
||||
sort($activeLanguages);
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$configurationManager->setLocalConfigurationValueByPath(
|
||||
'EXTCONF/lang',
|
||||
['availableLanguages' => $activeLanguages]
|
||||
);
|
||||
$activationArray = [];
|
||||
foreach ($activateArray as $activateIso) {
|
||||
$activationArray[] = $availableLanguages[$activateIso] . ' (' . $activateIso . ')';
|
||||
}
|
||||
$messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'These languages have been activated: ' . implode(', ', $activationArray)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$messageQueue->enqueue(
|
||||
new FlashMessage('Language with ISO code "' . $iso . '" not found or already active.', '', FlashMessage::ERROR)
|
||||
);
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate a language if no other active language depends on it
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function languagePacksDeactivateLanguageAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
$languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
|
||||
$locales = GeneralUtility::makeInstance(Locales::class);
|
||||
$availableLanguages = $languagePackService->getAvailableLanguages();
|
||||
$activeLanguages = $languagePackService->getActiveLanguages();
|
||||
$iso = $request->getParsedBody()['install']['iso'];
|
||||
if (empty($iso)) {
|
||||
throw new \RuntimeException('No iso code given', 1520109807);
|
||||
}
|
||||
$otherActiveLanguageDependencies = [];
|
||||
foreach ($activeLanguages as $activeLanguage) {
|
||||
if ($activeLanguage === $iso) {
|
||||
continue;
|
||||
}
|
||||
$dependencies = $locales->getLocaleDependencies($activeLanguage);
|
||||
if (in_array($iso, $dependencies, true)) {
|
||||
$otherActiveLanguageDependencies[] = $activeLanguage;
|
||||
}
|
||||
}
|
||||
if (!empty($otherActiveLanguageDependencies)) {
|
||||
// Error: Must disable dependencies first
|
||||
$dependentArray = [];
|
||||
foreach ($otherActiveLanguageDependencies as $dependency) {
|
||||
$dependentArray[] = $availableLanguages[$dependency] . ' (' . $dependency . ')';
|
||||
}
|
||||
$messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" can not be deactivated. These'
|
||||
. ' other languages depend on it and need to be deactivated before:'
|
||||
. implode(', ', $dependentArray),
|
||||
'',
|
||||
FlashMessage::ERROR
|
||||
)
|
||||
);
|
||||
} else {
|
||||
if (in_array($iso, $activeLanguages, true)) {
|
||||
// Deactivate this language
|
||||
$newActiveLanguages = [];
|
||||
foreach ($activeLanguages as $activeLanguage) {
|
||||
if ($activeLanguage === $iso) {
|
||||
continue;
|
||||
}
|
||||
$newActiveLanguages[] = $activeLanguage;
|
||||
}
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$configurationManager->setLocalConfigurationValueByPath(
|
||||
'EXTCONF/lang',
|
||||
['availableLanguages' => $newActiveLanguages]
|
||||
);
|
||||
$messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" has been deactivated'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$messageQueue->enqueue(
|
||||
new FlashMessage(
|
||||
'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" has not been deactivated',
|
||||
'',
|
||||
FlashMessage::ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a pack of one extension and one language
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function languagePacksUpdatePackAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->loadExtLocalconfDatabaseAndExtTables();
|
||||
$iso = $request->getParsedBody()['install']['iso'];
|
||||
$key = $request->getParsedBody()['install']['extension'];
|
||||
$languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'packResult' => $languagePackService->languagePackDownload($key, $iso)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set "last updated" time in registry for fully updated language packs.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function languagePacksUpdateIsoTimesAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$isos = $request->getParsedBody()['install']['isos'];
|
||||
$languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
|
||||
$languagePackService->setLastUpdatedIsoCode($isos);
|
||||
|
||||
// The cache manager is already instantiated in the install tool
|
||||
// with some hacked settings to disable caching of extbase and fluid.
|
||||
// We want a "fresh" object here to operate on a different cache setup.
|
||||
// cacheManager implements SingletonInterface, so the only way to get a "fresh"
|
||||
// instance is by circumventing makeInstance and/or the objectManager and
|
||||
// using new directly!
|
||||
$cacheManager = new \TYPO3\CMS\Core\Cache\CacheManager();
|
||||
$cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
|
||||
$cacheManager->getCache('l10n')->flush();
|
||||
|
||||
return new JsonResponse(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set 'uc' field of all backend users to empty string
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function resetBackendUserUcAction(): ResponseInterface
|
||||
{
|
||||
GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('be_users')
|
||||
->update('be_users')
|
||||
->set('uc', '')
|
||||
->execute();
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'Preferences of all backend users have been reset',
|
||||
'Reset preferences of all backend users'
|
||||
));
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\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 Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
|
||||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Install\Configuration\FeatureManager;
|
||||
use TYPO3\CMS\Install\Service\ExtensionConfigurationService;
|
||||
use TYPO3\CMS\Install\Service\LocalConfigurationValueService;
|
||||
|
||||
/**
|
||||
* Settings controller
|
||||
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SettingsController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* Main "show the cards" view
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function cardsAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Settings/Cards.html');
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change install tool password
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function changeInstallToolPasswordGetDataAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Settings/ChangeInstallToolPassword.html');
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view->assignMultiple([
|
||||
'changeInstallToolPasswordToken' => $formProtection->generateToken('installTool', 'changeInstallToolPassword'),
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change install tool password
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function changeInstallToolPasswordAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$password = $request->getParsedBody()['install']['password'] ?? '';
|
||||
$passwordCheck = $request->getParsedBody()['install']['passwordCheck'];
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
|
||||
if ($password !== $passwordCheck) {
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'Install tool password not changed. Given passwords do not match.',
|
||||
'',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} elseif (strlen($password) < 8) {
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'Install tool password not changed. Given password must be at least eight characters long.',
|
||||
'',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$configurationManager->setLocalConfigurationValueByPath(
|
||||
'BE/installToolPassword',
|
||||
$hashInstance->getHashedPassword($password)
|
||||
);
|
||||
$messageQueue->enqueue(new FlashMessage('Install tool password changed'));
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of possible and active system maintainers
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function systemMaintainerGetListAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Settings/SystemMaintainer.html');
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view->assignMultiple([
|
||||
'systemMaintainerWriteToken' => $formProtection->generateToken('installTool', 'systemMaintainerWrite'),
|
||||
'systemMaintainerIsDevelopmentContext' => GeneralUtility::getApplicationContext()->isDevelopment(),
|
||||
]);
|
||||
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
|
||||
// We have to respect the enable fields here by our own because no TCA is loaded
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable('be_users');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$users = $queryBuilder
|
||||
->select('uid', 'username', 'disable', 'starttime', 'endtime')
|
||||
->from('be_users')
|
||||
->where(
|
||||
$queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq('admin', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->neq('username', $queryBuilder->createNamedParameter('_cli_', \PDO::PARAM_STR))
|
||||
)
|
||||
)
|
||||
->orderBy('uid')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
$systemMaintainerList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? [];
|
||||
$systemMaintainerList = array_map('intval', $systemMaintainerList);
|
||||
$currentTime = time();
|
||||
foreach ($users as &$user) {
|
||||
$user['disable'] = $user['disable'] ||
|
||||
((int)$user['starttime'] !== 0 && $user['starttime'] > $currentTime) ||
|
||||
((int)$user['endtime'] !== 0 && $user['endtime'] < $currentTime);
|
||||
$user['isSystemMaintainer'] = in_array((int)$user['uid'], $systemMaintainerList, true);
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => [],
|
||||
'users' => $users,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write new system maintainer list
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function systemMaintainerWriteAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
// Sanitize given user list and write out
|
||||
$newUserList = [];
|
||||
$users = $request->getParsedBody()['install']['users'] ?? [];
|
||||
if (is_array($users)) {
|
||||
foreach ($users as $uid) {
|
||||
if (MathUtility::canBeInterpretedAsInteger($uid)) {
|
||||
$newUserList[] = (int)$uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
|
||||
$validatedUserList = $queryBuilder
|
||||
->select('uid')
|
||||
->from('be_users')
|
||||
->where(
|
||||
$queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq('admin', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($newUserList, Connection::PARAM_INT_ARRAY))
|
||||
)
|
||||
)->execute()->fetchAll();
|
||||
|
||||
$validatedUserList = array_column($validatedUserList, 'uid');
|
||||
$validatedUserList = array_map('intval', $validatedUserList);
|
||||
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$configurationManager->setLocalConfigurationValuesByPathValuePairs(
|
||||
['SYS/systemMaintainers' => $validatedUserList]
|
||||
);
|
||||
|
||||
$messages = [];
|
||||
if (empty($validatedUserList)) {
|
||||
$messages[] = new FlashMessage(
|
||||
'',
|
||||
'Set system maintainer list to an empty array',
|
||||
FlashMessage::INFO
|
||||
);
|
||||
} else {
|
||||
$messages[] = new FlashMessage(
|
||||
implode(', ', $validatedUserList),
|
||||
'New system maintainer uid list',
|
||||
FlashMessage::INFO
|
||||
);
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messages
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main LocalConfiguration card content
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function localConfigurationGetContentAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$localConfigurationValueService = new LocalConfigurationValueService();
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view = $this->initializeStandaloneView($request, 'Settings/LocalConfigurationGetContent.html');
|
||||
$view->assignMultiple([
|
||||
'localConfigurationWriteToken' => $formProtection->generateToken('installTool', 'localConfigurationWrite'),
|
||||
'localConfigurationData' => $localConfigurationValueService->getCurrentConfigurationData(),
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write given LocalConfiguration settings
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function localConfigurationWriteAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$settings = $request->getParsedBody()['install']['configurationValues'];
|
||||
if (!is_array($settings) || empty($settings)) {
|
||||
throw new \RuntimeException(
|
||||
'Expected value array not found',
|
||||
1502282283
|
||||
);
|
||||
}
|
||||
$localConfigurationValueService = new LocalConfigurationValueService();
|
||||
$messageQueue = $localConfigurationValueService->updateLocalConfigurationValues($settings);
|
||||
if (empty($messageQueue)) {
|
||||
$messageQueue->enqueue(new FlashMessage(
|
||||
'',
|
||||
'No values changed',
|
||||
FlashMessage::WARNING
|
||||
));
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messageQueue,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main preset card content
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function presetsGetContentAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$view = $this->initializeStandaloneView($request, 'Settings/PresetsGetContent.html');
|
||||
$presetFeatures = GeneralUtility::makeInstance(FeatureManager::class);
|
||||
$presetFeatures = $presetFeatures->getInitializedFeatures($request->getParsedBody()['install']['values'] ?? []);
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view->assignMultiple([
|
||||
'presetsActivateToken' => $formProtection->generateToken('installTool', 'presetsActivate'),
|
||||
// This action is called again from within the card itself if a custom image path is supplied
|
||||
'presetsGetContentToken' => $formProtection->generateToken('installTool', 'presetsGetContent'),
|
||||
'presetFeatures' => $presetFeatures,
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write selected presets
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function presetsActivateAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$messages = new FlashMessageQueue('install');
|
||||
$configurationManager = new ConfigurationManager();
|
||||
$featureManager = new FeatureManager();
|
||||
$configurationValues = $featureManager->getConfigurationForSelectedFeaturePresets($request->getParsedBody()['install']['values'] ?? []);
|
||||
if (!empty($configurationValues)) {
|
||||
$configurationManager->setLocalConfigurationValuesByPathValuePairs($configurationValues);
|
||||
$messageBody = [];
|
||||
foreach ($configurationValues as $configurationKey => $configurationValue) {
|
||||
$messageBody[] = '\'' . $configurationKey . '\' => \'' . $configurationValue . '\'';
|
||||
}
|
||||
$messages->enqueue(new FlashMessage(
|
||||
implode(', ', $messageBody),
|
||||
'Configuration written'
|
||||
));
|
||||
} else {
|
||||
$messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'No configuration change selected',
|
||||
FlashMessage::INFO
|
||||
));
|
||||
}
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messages,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a list of extensions with their configuration form.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function extensionConfigurationGetContentAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
// Extension configuration needs initialized $GLOBALS['LANG']
|
||||
Bootstrap::initializeLanguageObject();
|
||||
$extensionConfigurationService = new ExtensionConfigurationService();
|
||||
$extensionsWithConfigurations = [];
|
||||
$activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
|
||||
foreach ($activePackages as $extensionKey => $activePackage) {
|
||||
if (@file_exists($activePackage->getPackagePath() . 'ext_conf_template.txt')) {
|
||||
$extensionsWithConfigurations[$extensionKey] = [
|
||||
'packageInfo' => $activePackage,
|
||||
'configuration' => $extensionConfigurationService->getConfigurationPreparedForView($extensionKey),
|
||||
];
|
||||
}
|
||||
}
|
||||
ksort($extensionsWithConfigurations);
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view = $this->initializeStandaloneView($request, 'Settings/ExtensionConfigurationGetContent.html');
|
||||
$view->assignMultiple([
|
||||
'extensionsWithConfigurations' => $extensionsWithConfigurations,
|
||||
'extensionConfigurationWriteToken' => $formProtection->generateToken('installTool', 'extensionConfigurationWrite'),
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write extension configuration
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function extensionConfigurationWriteAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$extensionKey = $request->getParsedBody()['install']['extensionKey'];
|
||||
$configuration = $request->getParsedBody()['install']['extensionConfiguration'];
|
||||
$nestedConfiguration = [];
|
||||
foreach ($configuration as $configKey => $value) {
|
||||
$nestedConfiguration = ArrayUtility::setValueByPath($nestedConfiguration, $configKey, $value, '.');
|
||||
}
|
||||
(new ExtensionConfiguration())->set($extensionKey, '', $nestedConfiguration);
|
||||
$messages = [
|
||||
new FlashMessage(
|
||||
'Successfully saved configuration for extension "' . $extensionKey . '"',
|
||||
'',
|
||||
FlashMessage::OK
|
||||
)
|
||||
];
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messages,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render feature toggles
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function featuresGetContentAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$configurationDescription = GeneralUtility::makeInstance(YamlFileLoader::class)
|
||||
->load($configurationManager->getDefaultConfigurationDescriptionFileLocation());
|
||||
$allFeatures = $GLOBALS['TYPO3_CONF_VARS']['SYS']['features'] ?? [];
|
||||
$features = [];
|
||||
foreach ($allFeatures as $featureName => $featureValue) {
|
||||
// Only features that have a .yml description will be listed. There is currently no
|
||||
// way for extensions to extend this, so feature toggles of non-core extensions are
|
||||
// not listed here.
|
||||
if (isset($configurationDescription['SYS']['items']['features']['items'][$featureName]['description'])) {
|
||||
$default = $configurationManager->getDefaultConfigurationValueByPath('SYS/features/' . $featureName);
|
||||
$features[] = [
|
||||
'name' => $featureName,
|
||||
'description' => $configurationDescription['SYS']['items']['features']['items'][$featureName]['description'],
|
||||
'default' => $default,
|
||||
'value' => $featureValue,
|
||||
];
|
||||
}
|
||||
}
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
$view = $this->initializeStandaloneView($request, 'Settings/FeaturesGetContent.html');
|
||||
$view->assignMultiple([
|
||||
'features' => $features,
|
||||
'featuresSaveToken' => $formProtection->generateToken('installTool', 'featuresSave'),
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'html' => $view->render(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update feature toggles state
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function featuresSaveAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$enabledFeaturesFromPost = $request->getParsedBody()['install']['values'] ?? [];
|
||||
$allFeatures = array_keys($GLOBALS['TYPO3_CONF_VARS']['SYS']['features'] ?? []);
|
||||
$configurationDescription = GeneralUtility::makeInstance(YamlFileLoader::class)
|
||||
->load($configurationManager->getDefaultConfigurationDescriptionFileLocation());
|
||||
foreach ($allFeatures as $featureName) {
|
||||
// Only features that have a .yml description will be listed. There is currently no
|
||||
// way for extensions to extend this, so feature toggles of non-core extensions are
|
||||
// not considered.
|
||||
if (isset($configurationDescription['SYS']['items']['features']['items'][$featureName]['description'])) {
|
||||
if (isset($enabledFeaturesFromPost[$featureName])) {
|
||||
$configurationManager->enableFeature($featureName);
|
||||
} else {
|
||||
$configurationManager->disableFeature($featureName);
|
||||
}
|
||||
}
|
||||
}
|
||||
$messages = [
|
||||
new FlashMessage(
|
||||
'Successfully updated feature toggles',
|
||||
'',
|
||||
FlashMessage::OK
|
||||
)
|
||||
];
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'status' => $messages,
|
||||
]);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\CoreVersion;
|
||||
|
||||
class CoreRelease
|
||||
{
|
||||
protected const RELEASE_TYPE_REGULAR = 'regular';
|
||||
protected const RELEASE_TYPE_SECURITY = 'security';
|
||||
|
||||
protected $version;
|
||||
protected $date;
|
||||
protected $type;
|
||||
protected $checksum;
|
||||
protected $isElts;
|
||||
|
||||
public function __construct(string $version, \DateTimeInterface $date, string $type, string $checksum, bool $isElts = false)
|
||||
{
|
||||
$this->version = $version;
|
||||
$this->date = $date;
|
||||
$this->type = $type;
|
||||
$this->checksum = $checksum;
|
||||
$this->isElts = $isElts;
|
||||
}
|
||||
|
||||
public static function fromApiResponse(array $response): self
|
||||
{
|
||||
return new self($response['version'], new \DateTimeImmutable($response['date']), $response['type'], $response['tar_package']['sha1sum'], $response['elts'] ?? false);
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function getDate(): \DateTimeInterface
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function isSecurityUpdate(): bool
|
||||
{
|
||||
return $this->type === self::RELEASE_TYPE_SECURITY;
|
||||
}
|
||||
|
||||
public function getChecksum(): string
|
||||
{
|
||||
return $this->checksum;
|
||||
}
|
||||
|
||||
public function isElts(): bool
|
||||
{
|
||||
return $this->isElts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\CoreVersion;
|
||||
|
||||
class MaintenanceWindow
|
||||
{
|
||||
protected $communitySupport;
|
||||
protected $eltsSupport;
|
||||
|
||||
public function __construct(?\DateTimeInterface $communitySupport, ?\DateTimeInterface $eltsSupport)
|
||||
{
|
||||
$this->communitySupport = $communitySupport;
|
||||
$this->eltsSupport = $eltsSupport;
|
||||
}
|
||||
|
||||
public static function fromApiResponse(array $response): self
|
||||
{
|
||||
$maintainedUntil = isset($response['maintained_until']) ? new \DateTimeImmutable($response['maintained_until']) : null;
|
||||
$eltsUntil = isset($response['elts_until']) ? new \DateTimeImmutable($response['elts_until']) : null;
|
||||
|
||||
return new self($maintainedUntil, $eltsUntil);
|
||||
}
|
||||
|
||||
public function isSupportedByCommunity(): bool
|
||||
{
|
||||
return $this->isSupported($this->communitySupport);
|
||||
}
|
||||
|
||||
public function isSupportedByElts(): bool
|
||||
{
|
||||
return $this->isSupported($this->eltsSupport);
|
||||
}
|
||||
|
||||
protected function isSupported(?\DateTimeInterface $supportedUntil): bool
|
||||
{
|
||||
return $supportedUntil !== null
|
||||
&& (
|
||||
$supportedUntil >=
|
||||
new \DateTimeImmutable('now', new \DateTimeZone('UTC'))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\CoreVersion;
|
||||
|
||||
class MajorRelease
|
||||
{
|
||||
protected $version;
|
||||
protected $lts;
|
||||
protected $title;
|
||||
protected $maintenanceWindow;
|
||||
|
||||
public function __construct(string $version, ?string $lts, string $title, MaintenanceWindow $maintenanceWindow)
|
||||
{
|
||||
$this->version = $version;
|
||||
$this->lts = $lts;
|
||||
$this->title = $title;
|
||||
$this->maintenanceWindow = $maintenanceWindow;
|
||||
}
|
||||
|
||||
public static function fromApiResponse(array $response): self
|
||||
{
|
||||
$maintenanceWindow = MaintenanceWindow::fromApiResponse($response);
|
||||
$ltsVersion = isset($response['lts']) ? (string)$response['lts'] : null;
|
||||
|
||||
return new self((string)$response['version'], $ltsVersion, $response['title'], $maintenanceWindow);
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function getLts(): ?string
|
||||
{
|
||||
return $this->lts;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getMaintenanceWindow(): MaintenanceWindow
|
||||
{
|
||||
return $this->maintenanceWindow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install;
|
||||
|
||||
/*
|
||||
* 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 Install exception
|
||||
*/
|
||||
class Exception extends CoreException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface to be implemented by all classes which can search and find code places.
|
||||
*/
|
||||
interface CodeScannerInterface
|
||||
{
|
||||
/**
|
||||
* Each match is an array with detail information
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMatches(): array;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
/**
|
||||
* A visitor doing some counting.
|
||||
* It sums the number of ignored lines and lines of effective code.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class CodeStatistics extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* @var int True if a class statement has @extensionScannerIgnoreFile
|
||||
*/
|
||||
protected $isCurrentFileIgnored = 0;
|
||||
|
||||
/**
|
||||
* @var int Counts @extensionScannerIgnoreLine statements
|
||||
*/
|
||||
protected $numberOfIgnoreLines = 0;
|
||||
|
||||
/**
|
||||
* @var int Number of effective code lines - class and method statements, function calls ...
|
||||
*/
|
||||
protected $numberOfEffectiveCodeLines = 0;
|
||||
|
||||
/**
|
||||
* @var int Current line number given not is in, runtime helper var
|
||||
*/
|
||||
protected $currentLineNumber = 0;
|
||||
|
||||
/**
|
||||
* Called by PhpParser during traversal.
|
||||
*
|
||||
* @param Node $node Incoming node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
$startLineOfNode = $node->getAttribute('startLine');
|
||||
if ($startLineOfNode !== $this->currentLineNumber) {
|
||||
$this->currentLineNumber = $startLineOfNode;
|
||||
$this->numberOfEffectiveCodeLines++;
|
||||
|
||||
// Class statements may contain the @extensionScannerIgnoreFile statements
|
||||
if ($node instanceof Class_) {
|
||||
$comments = $node->getAttribute('comments');
|
||||
if (!empty($comments)) {
|
||||
foreach ($comments as $comment) {
|
||||
if (strstr($comment->getText(), '@extensionScannerIgnoreFile') !== false) {
|
||||
$this->isCurrentFileIgnored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First node of line may contain the @extensionScannerIgnoreLine comment
|
||||
$comments = $node->getAttribute('comments');
|
||||
if (!empty($comments)) {
|
||||
foreach ($comments as $comment) {
|
||||
if (strstr($comment->getText(), '@extensionScannerIgnoreLine') !== false) {
|
||||
$this->numberOfIgnoreLines++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if a @extensionScannerIgnoreFile has been found.
|
||||
* Called externally *after* traversing
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function isFileIgnored()
|
||||
{
|
||||
return $this->isCurrentFileIgnored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of "effective" code lines: No comments, no empty lines,
|
||||
* but "class" statements, "function" statements, "use xy", etc.
|
||||
* Called externally *after* traversing
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNumberOfEffectiveCodeLines()
|
||||
{
|
||||
return $this->numberOfEffectiveCodeLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of found @extensionScannerIgnoreLine comments
|
||||
* Called externally *after* traversing
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNumberOfIgnoredLines()
|
||||
{
|
||||
return $this->numberOfIgnoreLines;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\AbstractCoreMatcher;
|
||||
|
||||
/**
|
||||
* Create a fully qualified class name object from first argument of
|
||||
* GeneralUtility::makeInstance('My\\Package\\Class\\Name') if given as string
|
||||
* and not as My\Package\Class\Name::class language construct.
|
||||
*
|
||||
* This resolver is to be called after generic NameResolver::class, but before
|
||||
* other search and find visitors that implement CodeScannerInterface::class
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class GeneratorClassesResolver extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Create an fqdn object from first makeInstance argument if it is a String
|
||||
*
|
||||
* @param Node $node Incoming node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof StaticCall
|
||||
&& $node->class instanceof FullyQualified
|
||||
&& $node->class->toString() === GeneralUtility::class
|
||||
&& $node->name->name === 'makeInstance'
|
||||
&& isset($node->args[0]->value)
|
||||
&& $node->args[0]->value instanceof Expr
|
||||
) {
|
||||
if (null === $className = $this->resolveClassName($node->args[0]->value)) {
|
||||
return;
|
||||
}
|
||||
$node->args[0]->value = $className;
|
||||
$node->setAttribute(AbstractCoreMatcher::NODE_RESOLVED_AS, new New_(
|
||||
$className,
|
||||
// remove first argument (class name)
|
||||
array_slice($node->args, 1),
|
||||
$node->getAttributes()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected function resolveClassName(Expr $value): ?FullyQualified
|
||||
{
|
||||
if ($value instanceof String_) {
|
||||
// 'TYPO3\\CMS\\ClassName'
|
||||
return new FullyQualified($value->value, $value->getAttributes());
|
||||
}
|
||||
if ($value instanceof ClassConstFetch && $value->class instanceof FullyQualified) {
|
||||
// \TYPO3\CMS\ClassName::class
|
||||
return $value->class;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\ExtensionScanner\CodeScannerInterface;
|
||||
|
||||
/**
|
||||
* Single "core matcher" classes extend from this.
|
||||
* It brings a set of protected methods to help single matcher classes doing common stuff.
|
||||
* This abstract extends the nikic/php-parser NodeVisitorAbstract which implements the main
|
||||
* parser interface, and it implements the TYPO3 specific CodeScannerInterface to retrieve matches.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
abstract class AbstractCoreMatcher extends NodeVisitorAbstract implements CodeScannerInterface
|
||||
{
|
||||
public const NODE_RESOLVED_AS = 'nodeResolvedAs';
|
||||
|
||||
/**
|
||||
* Incoming main configuration array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $matcherDefinitions = [];
|
||||
|
||||
/**
|
||||
* @var array List of accumulated matches
|
||||
*/
|
||||
protected $matches = [];
|
||||
|
||||
/**
|
||||
* Helper property containing an array derived from $this->matcherDefinitions
|
||||
* created in __construct() if needed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $flatMatcherDefinitions = [];
|
||||
|
||||
/**
|
||||
* @var int Helper variable for ignored line detection
|
||||
*/
|
||||
protected $currentCodeLine = 0;
|
||||
|
||||
/**
|
||||
* @var bool True if line with $lastIgnoredLineNumber is ignored
|
||||
*/
|
||||
protected $isCurrentLineIgnored = false;
|
||||
|
||||
/**
|
||||
* @var bool True if the entire file is ignored due to a @extensionScannerIgnoreFile class comment
|
||||
*/
|
||||
protected $isFullFileIgnored = false;
|
||||
|
||||
/**
|
||||
* Return list of matches after processing
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMatches(): array
|
||||
{
|
||||
return $this->matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some matcher need specific keys in the array definition to work properly.
|
||||
* This method is called typically in __construct() of a matcher to
|
||||
* verify these are given.
|
||||
* This method is a measure against broken core configuration. It should be
|
||||
* pretty quick and is only called in __construct() once, no kitten should be harmed.
|
||||
*
|
||||
* This method works on $this->matcherDefinitions.
|
||||
*
|
||||
* @param array $requiredArrayKeys List of required keys for single matchers
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function validateMatcherDefinitions(array $requiredArrayKeys = [])
|
||||
{
|
||||
foreach ($this->matcherDefinitions as $key => $matcherDefinition) {
|
||||
$this->validateMatcherDefinitionKeys($key, $matcherDefinition, $requiredArrayKeys);
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateMatcherDefinitionKeys(string $key, array $matcherDefinition, array $requiredArrayKeys = []): void
|
||||
{
|
||||
// Each config must point to at least one .rst file
|
||||
if (empty($matcherDefinition['restFiles'])) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Each configuration must have at least one referenced "restFiles" entry. Offending key: ' . $key,
|
||||
1500496068
|
||||
);
|
||||
}
|
||||
foreach ($matcherDefinition['restFiles'] as $file) {
|
||||
if (empty($file)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Empty restFiles definition',
|
||||
1500735983
|
||||
);
|
||||
}
|
||||
}
|
||||
// Config broken if not all required array keys are specified in config
|
||||
$sharedArrays = array_intersect(array_keys($matcherDefinition), $requiredArrayKeys);
|
||||
if (count($sharedArrays) !== count($requiredArrayKeys)) {
|
||||
$missingKeys = array_diff($requiredArrayKeys, array_keys($matcherDefinition));
|
||||
throw new \InvalidArgumentException(
|
||||
'Required matcher definitions missing: ' . implode(', ', $missingKeys) . ' offending key: ' . $key,
|
||||
1500492001
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize helper lookup array $this->flatMatcherDefinitions.
|
||||
* For class\name->foo matcherDefinitions, it creates a helper array
|
||||
* containing only the method name as array keys for "weak" matches.
|
||||
*
|
||||
* If methods with the same name from different classes are defined,
|
||||
* a "candidate" array is created containing details of single possible
|
||||
* matches for further analysis.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function initializeFlatMatcherDefinitions()
|
||||
{
|
||||
$methodNameArray = [];
|
||||
foreach ($this->matcherDefinitions as $classAndMethod => $details) {
|
||||
$method = GeneralUtility::trimExplode('::', $classAndMethod);
|
||||
if (count($method) !== 2) {
|
||||
$method = GeneralUtility::trimExplode('->', $classAndMethod);
|
||||
}
|
||||
if (count($method) !== 2) {
|
||||
throw new \RuntimeException(
|
||||
'Keys in $this->matcherDefinitions must have a Class\Name->method or Class\Name::method structure',
|
||||
1500557309
|
||||
);
|
||||
}
|
||||
$method = $method[1];
|
||||
if (!array_key_exists($method, $methodNameArray)) {
|
||||
$methodNameArray[$method]['candidates'] = [];
|
||||
}
|
||||
$methodNameArray[$method]['candidates'][] = $details;
|
||||
}
|
||||
$this->flatMatcherDefinitions = $methodNameArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if one argument is given as "...$someArray".
|
||||
* If so, it kinda defeats any "argument count" approach.
|
||||
*
|
||||
* @param array $arguments List of arguments
|
||||
* @return bool
|
||||
*/
|
||||
protected function isArgumentUnpackingUsed(array $arguments = []): bool
|
||||
{
|
||||
foreach ($arguments as $arg) {
|
||||
if ($arg->unpack === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a comment before a statement is
|
||||
* marked as "@extensionScannerIgnoreLine"
|
||||
*
|
||||
* @param Node $node
|
||||
* @return bool
|
||||
*/
|
||||
protected function isLineIgnored(Node $node): bool
|
||||
{
|
||||
// Early return if this line is marked as ignored
|
||||
$startLineOfNode = $node->getAttribute('startLine');
|
||||
if ($startLineOfNode === $this->currentCodeLine) {
|
||||
return $this->isCurrentLineIgnored;
|
||||
}
|
||||
|
||||
$currentLineIsIgnored = false;
|
||||
if ($startLineOfNode !== $this->currentCodeLine) {
|
||||
$this->currentCodeLine = $startLineOfNode;
|
||||
// First node of a new line may contain the annotation
|
||||
$comments = $node->getAttribute('comments');
|
||||
if (!empty($comments)) {
|
||||
foreach ($comments as $comment) {
|
||||
if (strstr($comment->getText(), '@extensionScannerIgnoreLine') !== false) {
|
||||
$this->isCurrentLineIgnored = true;
|
||||
$currentLineIsIgnored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $currentLineIsIgnored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the node is ignored since the entire file is ignored.
|
||||
* Sets ignore status if a class node is given having the annotation.
|
||||
*
|
||||
* @param Node $node
|
||||
* @return bool
|
||||
*/
|
||||
protected function isFileIgnored(Node $node): bool
|
||||
{
|
||||
if ($this->isFullFileIgnored) {
|
||||
return true;
|
||||
}
|
||||
$currentFileIsIgnored = false;
|
||||
if ($node instanceof Class_) {
|
||||
$comments = $node->getAttribute('comments');
|
||||
if (!empty($comments)) {
|
||||
foreach ($comments as $comment) {
|
||||
if (strstr($comment->getText(), '@extensionScannerIgnoreFile') !== false) {
|
||||
$this->isFullFileIgnored = true;
|
||||
$currentFileIsIgnored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $currentFileIsIgnored;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Find usages of dropped configuration values and hook registrations.
|
||||
* Matches on "last" key only.
|
||||
* Definition of $GLOBALS['foo']['bar'] and usage as $foo['bar'] matches.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ArrayDimensionMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Initialize "flat" matcher array from matcher definitions.
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
$this->initializeLastArrayKeyNameArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof ArrayDimFetch
|
||||
&& isset($node->dim->value)
|
||||
&& array_key_exists($node->dim->value, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Access to array key "' . $node->dim->value . '"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
foreach ($this->flatMatcherDefinitions[$node->dim->value]['candidates'] as $candidate) {
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare 'lastKey' => [$details] array in flatMatcherDefinitions
|
||||
*/
|
||||
protected function initializeLastArrayKeyNameArray()
|
||||
{
|
||||
$methodNameArray = [];
|
||||
foreach ($this->matcherDefinitions as $fullArrayString => $details) {
|
||||
// Goal: find last part "foobar" of an array path "$foo['bar']['foobar']"
|
||||
// Reverse string $foo['bar']['foobar']
|
||||
$lastKey = strrev($fullArrayString);
|
||||
// Cut off "['"
|
||||
$lastKey = substr($lastKey, 2);
|
||||
$lastKey = GeneralUtility::trimExplode('\'[', $lastKey);
|
||||
// Last key name
|
||||
$lastKey = $lastKey[0];
|
||||
// And reverse key name again
|
||||
$lastKey = strrev($lastKey);
|
||||
|
||||
if (!array_key_exists($lastKey, $methodNameArray)) {
|
||||
$methodNameArray[$lastKey]['candidates'] = [];
|
||||
}
|
||||
$methodNameArray[$lastKey]['candidates'][] = $details;
|
||||
}
|
||||
$this->flatMatcherDefinitions = $methodNameArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
|
||||
/**
|
||||
* Match access to a one dimensional $GLOBAL array
|
||||
* Example "$GLOBALS['TYPO3_DB']"
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ArrayGlobalMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Initialize "flat" matcher array from matcher definitions.
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof ArrayDimFetch
|
||||
&& $node->var instanceof Variable
|
||||
&& $node->var->name === 'GLOBALS'
|
||||
&& $node->dim instanceof String_
|
||||
&& array_key_exists('$GLOBALS[\'' . $node->dim->value . '\']', $this->matcherDefinitions)
|
||||
) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions['$GLOBALS[\'' . $node->dim->value . '\']']['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Access to array global array "' . $node->dim->value . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of class constants.
|
||||
*
|
||||
* Test for "Class\Name::THE_CONSTANT", matches are considered "strong"
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ClassConstantMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof ClassConstFetch
|
||||
&& $node->class instanceof FullyQualified
|
||||
&& array_key_exists($node->class->toString() . '::' . $node->name, $this->matcherDefinitions)
|
||||
) {
|
||||
// No weak test implemented - combination class::const name tested
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$node->class->toString() . '::' . $node->name]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to class constant "' . $node->class->toString() . '::' . $node->name . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of class / interface names which are entirely deprecated or removed
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ClassNameMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Default constructor validates matcher definition.
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node Given node to test
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof FullyQualified
|
||||
) {
|
||||
$fullyQualifiedClassName = $node->toString();
|
||||
if (array_key_exists($fullyQualifiedClassName, $this->matcherDefinitions)) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$fullyQualifiedClassName]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Usage of class "' . $fullyQualifiedClassName . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
|
||||
/**
|
||||
* Find usages of class constants.
|
||||
*
|
||||
* Test for "THE_CONSTANT", matches are considered "strong"
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ConstantMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof ConstFetch
|
||||
&& array_key_exists($node->name->toString(), $this->matcherDefinitions)
|
||||
) {
|
||||
// Access to constants is detected as strong match
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$node->name->toString()]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to global constant "' . $node->name->toString() . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
|
||||
/**
|
||||
* Finds invocations to class constructors and the amount of passed arguments.
|
||||
* This matcher supports direct `new MyClass(123)` invocations as well as delegated
|
||||
* calls to `GeneralUtility::makeInstance(MyClass::class, 123)` using `GeneratorClassResolver`.
|
||||
*
|
||||
* These configuration property names are handled independently:
|
||||
* + numberOfMandatoryArguments
|
||||
* + maximumNumberOfArguments
|
||||
* + unusedArgumentNumbers
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ConstructorArgumentMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
protected const TOPIC_TYPE_REQUIRED = 'required';
|
||||
protected const TOPIC_TYPE_DROPPED = 'dropped';
|
||||
protected const TOPIC_TYPE_CALLED = 'called';
|
||||
protected const TOPIC_TYPE_UNUSED = 'unused';
|
||||
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitionsTopicRequirements([
|
||||
self::TOPIC_TYPE_REQUIRED => ['numberOfMandatoryArguments'],
|
||||
self::TOPIC_TYPE_DROPPED => ['maximumNumberOfArguments'],
|
||||
self::TOPIC_TYPE_CALLED => ['numberOfMandatoryArguments', 'maximumNumberOfArguments'],
|
||||
self::TOPIC_TYPE_UNUSED => ['unusedArgumentNumbers'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($this->isFileIgnored($node) || $this->isLineIgnored($node)) {
|
||||
return;
|
||||
}
|
||||
$resolvedNode = $node->getAttribute(self::NODE_RESOLVED_AS, null) ?? $node;
|
||||
if (!$resolvedNode instanceof New_
|
||||
|| !isset($resolvedNode->class)
|
||||
|| is_object($node->class) && !method_exists($node->class, '__toString')
|
||||
|| !array_key_exists((string)$resolvedNode->class, $this->matcherDefinitions)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is lower than numberOfMandatoryArguments
|
||||
if ($this->isArgumentUnpackingUsed($resolvedNode->args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
// $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
$this->handleRequiredArguments($node, $resolvedNode);
|
||||
$this->handleDroppedArguments($node, $resolvedNode);
|
||||
$this->handleCalledArguments($node, $resolvedNode);
|
||||
$this->handleUnusedArguments($node, $resolvedNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
* @param Node $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
* @return bool
|
||||
*/
|
||||
protected function handleRequiredArguments(Node $node, Node $resolvedNode): bool
|
||||
{
|
||||
$className = (string)$resolvedNode->class;
|
||||
$candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_REQUIRED] ?? null;
|
||||
$mandatoryArguments = $candidate['numberOfMandatoryArguments'] ?? null;
|
||||
$numberOfArguments = count($resolvedNode->args);
|
||||
|
||||
if ($candidate === null || $numberOfArguments >= $mandatoryArguments) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->matches[] = [
|
||||
'restFiles' => $candidate['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => sprintf(
|
||||
'%s::__construct requires at least %d arguments (%d given).',
|
||||
$className,
|
||||
$mandatoryArguments,
|
||||
$numberOfArguments
|
||||
),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
* @param Node $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
* @return bool
|
||||
*/
|
||||
protected function handleDroppedArguments(Node $node, Node $resolvedNode): bool
|
||||
{
|
||||
$className = (string)$resolvedNode->class;
|
||||
$candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_DROPPED] ?? null;
|
||||
$maximumArguments = $candidate['maximumNumberOfArguments'] ?? null;
|
||||
$numberOfArguments = count($resolvedNode->args);
|
||||
|
||||
if ($candidate === null || $numberOfArguments <= $maximumArguments) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->matches[] = [
|
||||
'restFiles' => $candidate['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => sprintf(
|
||||
'%s::__construct supports only %d arguments (%d given).',
|
||||
$className,
|
||||
$maximumArguments,
|
||||
$numberOfArguments
|
||||
),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
* @param Node $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
* @return bool
|
||||
*/
|
||||
protected function handleCalledArguments(Node $node, Node $resolvedNode): bool
|
||||
{
|
||||
$className = (string)$resolvedNode->class;
|
||||
$candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_CALLED] ?? null;
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($resolvedNode->args);
|
||||
$mandatoryArguments = $candidate['numberOfMandatoryArguments'] ?? null;
|
||||
$maximumArguments = $candidate['maximumNumberOfArguments'] ?? null;
|
||||
$numberOfArguments = count($resolvedNode->args);
|
||||
|
||||
if ($candidate === null
|
||||
|| !$isArgumentUnpackingUsed
|
||||
&& ($numberOfArguments < $mandatoryArguments || $numberOfArguments > $maximumArguments)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->matches[] = [
|
||||
'restFiles' => $candidate['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => sprintf(
|
||||
'%s::__construct being called (%d arguments given).',
|
||||
$className,
|
||||
$numberOfArguments
|
||||
),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
* @param Node $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
* @return bool
|
||||
*/
|
||||
protected function handleUnusedArguments(Node $node, Node $resolvedNode): bool
|
||||
{
|
||||
$className = (string)$resolvedNode->class;
|
||||
$candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_UNUSED] ?? null;
|
||||
// values in array (if any) are actual position counts
|
||||
// e.g. `[2, 4]` refers to internal argument indexes `[1, 3]`
|
||||
$unusedArgumentPositions = $candidate['unusedArgumentNumbers'] ?? null;
|
||||
|
||||
if ($candidate === null || empty($unusedArgumentPositions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$arguments = $resolvedNode->args;
|
||||
// keeping positions having argument values that are not null
|
||||
$unusedArgumentPositions = array_filter(
|
||||
$unusedArgumentPositions,
|
||||
function (int $position) use ($arguments) {
|
||||
$index = $position - 1;
|
||||
return isset($arguments[$index]->value)
|
||||
&& !$arguments[$index]->value instanceof ConstFetch
|
||||
&& (
|
||||
!isset($arguments[$index]->value->name->name->parts[0])
|
||||
|| $arguments[$index]->value->name->name->parts[0] !== null
|
||||
);
|
||||
}
|
||||
);
|
||||
if (empty($unusedArgumentPositions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->matches[] = [
|
||||
'restFiles' => $candidate['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => sprintf(
|
||||
'%s::__construct was called with argument positions %s not being null.',
|
||||
$className,
|
||||
implode(', ', $unusedArgumentPositions)
|
||||
),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function validateMatcherDefinitionsTopicRequirements(array $topicRequirements): void
|
||||
{
|
||||
foreach ($this->matcherDefinitions as $key => $matcherDefinition) {
|
||||
foreach ($topicRequirements as $topic => $requiredArrayKeys) {
|
||||
if (empty($matcherDefinition[$topic])) {
|
||||
continue;
|
||||
}
|
||||
$this->validateMatcherDefinitionKeys($key, $matcherDefinition[$topic], $requiredArrayKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of global function calls which were removed / deprecated.
|
||||
* This is a strong match.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class FunctionCallMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments', 'maximumNumberOfArguments']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "removedFunction()" (strong match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof FuncCall
|
||||
&& $node->name instanceof FullyQualified
|
||||
&& array_key_exists($node->name->toString(), $this->matcherDefinitions)
|
||||
) {
|
||||
$functionName = $node->name->toString();
|
||||
$matchDefinition = $this->matcherDefinitions[$functionName];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
if ($isArgumentUnpackingUsed
|
||||
|| ($numberOfArguments >= $matchDefinition['numberOfMandatoryArguments']
|
||||
&& $numberOfArguments <= $matchDefinition['maximumNumberOfArguments'])
|
||||
) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $matchDefinition['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to function "' . $functionName . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
|
||||
/**
|
||||
* Matches interface method arguments which have been dropped.
|
||||
*
|
||||
* This does *not* test if a class implements an interface.
|
||||
* The scanner only looks for:
|
||||
* - Class method names not having specified number of arguments
|
||||
* - Method calls with given method name not having this number of arguments
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class InterfaceMethodChangedMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Default constructor validates config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
// newNumberOfArguments must exist in all matcherDefinitions
|
||||
$this->validateMatcherDefinitions(['newNumberOfArguments']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "public function like($arg1, $arg2, $arg3) {}" (weak match)
|
||||
* Test for "->like($arg1, $arg2, $arg3); (weak match)
|
||||
*
|
||||
* @param Node $node
|
||||
* @return void|null
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($this->isFileIgnored($node) || $this->isLineIgnored($node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Match method name of a class, must be public, wouldn't make sense as interface if protected/private
|
||||
if ($node instanceof ClassMethod
|
||||
&& array_key_exists($node->name->name, $this->matcherDefinitions)
|
||||
&& $node->flags & Class_::MODIFIER_PUBLIC // public
|
||||
&& ($node->flags & Class_::MODIFIER_STATIC) !== Class_::MODIFIER_STATIC // not static
|
||||
) {
|
||||
$methodName = $node->name->name;
|
||||
$numberOfUsedArguments = 0;
|
||||
if (isset($node->params) && is_array($node->params)) {
|
||||
$numberOfUsedArguments = count($node->params);
|
||||
}
|
||||
$numberOfAllowedArguments = $this->matcherDefinitions[$methodName]['newNumberOfArguments'];
|
||||
if ($numberOfUsedArguments > $numberOfAllowedArguments) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$methodName]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Implementation of dropped interface argument for method "' . $methodName . '()"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Match method call (not static) with number of arguments
|
||||
if ($node instanceof MethodCall
|
||||
&& array_key_exists($node->name->name, $this->matcherDefinitions)
|
||||
) {
|
||||
$methodName = $node->name->name;
|
||||
$numberOfUsedArguments = 0;
|
||||
if (isset($node->args) && is_array($node->args)) {
|
||||
$numberOfUsedArguments = count($node->args);
|
||||
}
|
||||
// @todo: Test for argument unpacking
|
||||
$numberOfAllowedArguments = $this->matcherDefinitions[$methodName]['newNumberOfArguments'];
|
||||
if ($numberOfUsedArguments > $numberOfAllowedArguments) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$methodName]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to interface method "' . $methodName . '()"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Comment\Doc;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
|
||||
/**
|
||||
* Find usages of method annotations
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodAnnotationMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for method annotations (strong match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof ClassMethod
|
||||
&& ($docComment = $node->getDocComment()) instanceof Doc
|
||||
&& !$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
) {
|
||||
$isPossibleMatch = false;
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
|
||||
$matches = [];
|
||||
preg_match_all(
|
||||
'/\s*\s@(?<annotations>[^\s.]*).*\n/',
|
||||
$docComment->getText(),
|
||||
$matches
|
||||
);
|
||||
|
||||
foreach ($matches['annotations'] as $annotation) {
|
||||
$annotation = '@' . $annotation;
|
||||
|
||||
if (!isset($this->matcherDefinitions[$annotation])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method "' . $node->name . '" uses an ' . $annotation . ' annotation.';
|
||||
$match['restFiles'] = array_unique(array_merge(
|
||||
$match['restFiles'],
|
||||
$this->matcherDefinitions[$annotation]['restFiles']
|
||||
));
|
||||
}
|
||||
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
|
||||
/**
|
||||
* Find usages of method calls which changed signature and dropped arguments,
|
||||
* but are called with more arguments.
|
||||
* This is a "weak" match since we're just testing for method name
|
||||
* but not connected class.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentDroppedMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof MethodCall
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is higher than maximumNumberOfArguments
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments > $candidate['maximumNumberOfArguments']
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method "' . $node->name->name . '()" supports only ' . $candidate['maximumNumberOfArguments'] . ' arguments.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of static method calls which were removed / deprecated.
|
||||
* This is a "strong" match if class name is given and "weak" if not.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentDroppedStaticMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Match static method call
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof StaticCall
|
||||
) {
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
if ($node->class instanceof FullyQualified) {
|
||||
// 'Foo\Bar::aMethod()' -> strong match
|
||||
$fqdnClassWithMethod = $node->class->toString() . '::' . $node->name->name;
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& array_key_exists($fqdnClassWithMethod, $this->matcherDefinitions)
|
||||
&& count($node->args) > $this->matcherDefinitions[$fqdnClassWithMethod]['maximumNumberOfArguments']
|
||||
) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$fqdnClassWithMethod]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Method "' . $node->name->name . '()" supports only '
|
||||
. $this->matcherDefinitions[$fqdnClassWithMethod]['maximumNumberOfArguments']
|
||||
. ' arguments.',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
} elseif ($node->class instanceof Variable
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is higher than maximumNumberOfArguments
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments > $candidate['maximumNumberOfArguments']
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method "' . $node->name->name . '()" supports only '
|
||||
. $candidate['maximumNumberOfArguments'] . ' arguments.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
|
||||
/**
|
||||
* Find usages of method calls which changed signature and added required arguments.
|
||||
* This is a "weak" match since we're just testing for method name
|
||||
* but not connected class.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentRequiredMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof MethodCall
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is lower than numberOfMandatoryArguments
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments < $candidate['numberOfMandatoryArguments']
|
||||
&& $numberOfArguments <= $candidate['maximumNumberOfArguments']
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method ' . $node->name->name . '() needs at least ' . $candidate['numberOfMandatoryArguments'] . ' arguments.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of static method calls which gained new mandatory arguments.
|
||||
* This is a "strong" match if class name is given and "weak" if not.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentRequiredStaticMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments', 'maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "::function($1, $2, $3)" (strong match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Match static method call
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof StaticCall
|
||||
) {
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
if ($node->class instanceof FullyQualified) {
|
||||
// 'Foo\Bar::aMethod()' -> strong match
|
||||
$fqdnClassWithMethod = $node->class->toString() . '::' . $node->name->name;
|
||||
$numberOfArguments = count($node->args);
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& array_key_exists($fqdnClassWithMethod, $this->matcherDefinitions)
|
||||
&& $numberOfArguments < $this->matcherDefinitions[$fqdnClassWithMethod]['numberOfMandatoryArguments']
|
||||
// maximum number of arguments is just a measure agains false positives
|
||||
&& $numberOfArguments <= $this->matcherDefinitions[$fqdnClassWithMethod]['maximumNumberOfArguments']
|
||||
) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$fqdnClassWithMethod]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Method "' . $node->name->name . '()" needs at least '
|
||||
. $this->matcherDefinitions[$fqdnClassWithMethod]['numberOfMandatoryArguments']
|
||||
. ' arguments.',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
} elseif ($node->class instanceof Variable
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is lesser than numberOfMandatoryArguments
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments < $candidate['numberOfMandatoryArguments']
|
||||
// maximum number of arguments is just a measure agains false positives
|
||||
&& $numberOfArguments <= $candidate['maximumNumberOfArguments']
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method "' . $node->name->name . '()" needs at least '
|
||||
. $candidate['numberOfMandatoryArguments'] . ' arguments.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
|
||||
/**
|
||||
* Match method usages where arguments "in between" are unused but not given as "null":
|
||||
*
|
||||
* public function foo($arg1, $unsused1 = null, $unused2 = null, $arg4)
|
||||
* but called with:
|
||||
* ->foo('arg1', 'notNull', null, 'arg4');
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentUnusedMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['unusedArgumentNumbers']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof MethodCall
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
foreach ($candidate['unusedArgumentNumbers'] as $droppedArgumentNumber) {
|
||||
// A method call is considered a match if name matches, unpacking is not used
|
||||
// and the registered argument is not given as null.
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments >= $droppedArgumentNumber
|
||||
&& !($node->args[$droppedArgumentNumber - 1]->value instanceof ConstFetch)
|
||||
&& (!isset($node->args[$droppedArgumentNumber - 1]->value->name->name->parts[0])
|
||||
|| $node->args[$droppedArgumentNumber - 1]->value->name->name->parts[0] !== null)
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Call to method "' . $node->name->name . '()" with'
|
||||
. ' argument ' . $droppedArgumentNumber . ' not given as null.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
|
||||
/**
|
||||
* Find usages of method calls which were removed / deprecated.
|
||||
* This is a "weak" match since we're just testing for method name
|
||||
* but not connected class.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodCallMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments', 'maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof MethodCall
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to method "' . $node->name->name . '()"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is called with argument unpacking, or
|
||||
// if the number of given arguments is within range of mandatory / max number of arguments
|
||||
if ($isArgumentUnpackingUsed
|
||||
|| ($numberOfArguments >= $candidate['numberOfMandatoryArguments']
|
||||
&& $numberOfArguments <= $candidate['maximumNumberOfArguments'])
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of static method calls which were removed / deprecated.
|
||||
*
|
||||
* This match is performed either is case of a direct "foo\bar::aMethod()" call
|
||||
* as "strong" match, or as only "::aMethod()" as "weak" match.
|
||||
*
|
||||
* As additional indicator, the number of required, mandatory arguments is
|
||||
* recognized: If calling a static method as "$foo::aMethod($arg1), but the
|
||||
* method needs two arguments, this is *not* considered a match. This would
|
||||
* have raised a fatal PHP error anyway and this is nothing we test here.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodCallStaticMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config and prepare weak matcher array
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments', 'maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "foo\bar::deprecated()" (strong match)
|
||||
* Test for "::deprecated()" (weak match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Static call, not method call
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof StaticCall
|
||||
) {
|
||||
if ($node->class instanceof FullyQualified) {
|
||||
// 'Foo\Bar::deprecated()' -> strong match
|
||||
$fqdnClassWithMethod = $node->class->toString() . '::' . $node->name->name;
|
||||
if (array_key_exists($fqdnClassWithMethod, $this->matcherDefinitions)) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$fqdnClassWithMethod]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Use of static class method call "' . $fqdnClassWithMethod . '()"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
} elseif ($node->class instanceof Variable
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Use of static class method call "' . $node->name->name . '()"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is called with argument unpacking, or
|
||||
// if the number of given arguments is within range of mandatory / max number of arguments
|
||||
if ($isArgumentUnpackingUsed
|
||||
|| ($numberOfArguments >= $candidate['numberOfMandatoryArguments']
|
||||
&& $numberOfArguments <= $candidate['maximumNumberOfArguments'])
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Comment\Doc;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PhpParser\Node\Stmt\PropertyProperty;
|
||||
|
||||
/**
|
||||
* Find usages of property annotations
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PropertyAnnotationMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for property annotations (strong match)
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Property
|
||||
&& ($property = reset($node->props)) instanceof PropertyProperty
|
||||
&& ($docComment = $node->getDocComment()) instanceof Doc
|
||||
&& !$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
) {
|
||||
/** @var PropertyProperty $property */
|
||||
$isPossibleMatch = false;
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $property->getAttribute('startLine'),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
|
||||
$matches = [];
|
||||
preg_match_all(
|
||||
'/\s*\s@(?<annotations>[^\s.]*).*\n/',
|
||||
$docComment->getText(),
|
||||
$matches
|
||||
);
|
||||
|
||||
foreach ($matches['annotations'] as $annotation) {
|
||||
$annotation = '@' . $annotation;
|
||||
|
||||
if (!isset($this->matcherDefinitions[$annotation])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Property "' . $property->name . '" uses an ' . $annotation . ' annotation.';
|
||||
$match['restFiles'] = array_unique(array_merge(
|
||||
$match['restFiles'],
|
||||
$this->matcherDefinitions[$annotation]['restFiles']
|
||||
));
|
||||
}
|
||||
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
|
||||
/**
|
||||
* Find usages of properties which have been deprecated or removed.
|
||||
* Useful if abstract classes remove properties.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PropertyExistsStaticMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config and prepare flat mach array
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof Property
|
||||
&& $node->isStatic()
|
||||
&& !$node->isPrivate()
|
||||
&& array_key_exists($node->props[0]->name->name, $this->matcherDefinitions)
|
||||
) {
|
||||
$propertyName = $node->props[0]->name->name;
|
||||
$match = [
|
||||
'restFiles' => $this->matcherDefinitions[$propertyName]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Use of property "' . $node->props[0]->name->name . '"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
/**
|
||||
* Find usages of properties which have been made protected and are
|
||||
* not called in $this context.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PropertyProtectedMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config and prepare flat mach array
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof PropertyFetch
|
||||
&& $node->name instanceof Identifier
|
||||
&& $node->var->name !== 'this'
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Fetch of property "' . $node->name->name . '"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\Node;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
/**
|
||||
* Find usages of properties which were removed / deprecated.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PropertyPublicMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node
|
||||
*/
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
// Match property access (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof PropertyFetch
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Fetch of property "' . $node->name->name . '"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php;
|
||||
|
||||
/*
|
||||
* 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 PhpParser\NodeVisitor;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\ExtensionScanner\CodeScannerInterface;
|
||||
|
||||
/**
|
||||
* Factory preparing matcher instances
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MatcherFactory
|
||||
{
|
||||
/**
|
||||
* Create matcher instances and hand over configuration.
|
||||
*
|
||||
* @param array $matcherConfigurations Incoming configuration array
|
||||
* @return NodeVisitor[]&CodeScannerInterface[]
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function createAll(array $matcherConfigurations)
|
||||
{
|
||||
$instances = [];
|
||||
foreach ($matcherConfigurations as $matcherConfiguration) {
|
||||
if (empty($matcherConfiguration['class'])) {
|
||||
throw new \RuntimeException(
|
||||
'Each matcher must have a class name',
|
||||
1501415721
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($matcherConfiguration['configurationFile']) && !isset($matcherConfiguration['configurationArray'])) {
|
||||
throw new \RuntimeException(
|
||||
'Each matcher must have either a configurationFile or configurationArray defined',
|
||||
1501416365
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($matcherConfiguration['configurationFile']) && isset($matcherConfiguration['configurationArray'])) {
|
||||
throw new \RuntimeException(
|
||||
'Having both a configurationFile and configurationArray is invalid',
|
||||
1501419367
|
||||
);
|
||||
}
|
||||
|
||||
$configuration = [];
|
||||
if (isset($matcherConfiguration['configurationFile'])) {
|
||||
$configuration = GeneralUtility::getFileAbsFileName($matcherConfiguration['configurationFile']);
|
||||
if (empty($configuration) || !is_file($configuration)) {
|
||||
throw new \RuntimeException(
|
||||
'Configuration file ' . $matcherConfiguration['configurationFile'] . ' not found',
|
||||
1501509605
|
||||
);
|
||||
}
|
||||
$configuration = require $configuration;
|
||||
if (!is_array($configuration)) {
|
||||
throw new \RuntimeException(
|
||||
'Configuration file ' . $matcherConfiguration['configurationFile'] . ' must return an array',
|
||||
1501509548
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($matcherConfiguration['configurationArray'])) {
|
||||
if (!is_array($matcherConfiguration['configurationArray'])) {
|
||||
throw new \RuntimeException(
|
||||
'Configuration array ' . $matcherConfiguration['configurationArray'] . ' must not be empty',
|
||||
1501509738
|
||||
);
|
||||
}
|
||||
$configuration = $matcherConfiguration['configurationArray'];
|
||||
}
|
||||
|
||||
$matcherInstance = new $matcherConfiguration['class']($configuration);
|
||||
if (!$matcherInstance instanceof CodeScannerInterface
|
||||
|| !$matcherInstance instanceof NodeVisitor) {
|
||||
throw new \RuntimeException(
|
||||
'Matcher ' . $matcherConfiguration['class'] . ' must implement CodeScannerInterface'
|
||||
. ' and NodeVisitor',
|
||||
1501510168
|
||||
);
|
||||
}
|
||||
$instances[] = $matcherInstance;
|
||||
}
|
||||
return $instances;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessage;
|
||||
|
||||
/**
|
||||
* Abstract node implements common methods
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
abstract class AbstractNode
|
||||
{
|
||||
/**
|
||||
* @var string Name
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* @var string|null Target permissions for unix, eg. '2775' or '0664' (4 characters string)
|
||||
*/
|
||||
protected $targetPermission;
|
||||
|
||||
/**
|
||||
* @var NodeInterface|null Parent object of this structure node
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* @var array Directories and root may have children, files and link always empty array
|
||||
*/
|
||||
protected $children = [];
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string Name
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get target permission
|
||||
*
|
||||
* Make sure to call octdec on the value when passing this to chmod
|
||||
*
|
||||
* @return string Permissions as a 4 character octal string, i.e. 2775 or 0644
|
||||
*/
|
||||
protected function getTargetPermission()
|
||||
{
|
||||
return $this->targetPermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set target permission
|
||||
*
|
||||
* @param string $permission Permissions as a 4 character octal string, i.e. 2775 or 0644
|
||||
*/
|
||||
protected function setTargetPermission($permission)
|
||||
{
|
||||
// Normalize the permission string to "4 characters", padding with leading "0" if necessary:
|
||||
$permission = substr($permission, 0, 4);
|
||||
$permission = str_pad($permission, 4, '0', STR_PAD_LEFT);
|
||||
$this->targetPermission = $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get children
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getChildren()
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parent
|
||||
*
|
||||
* @return NodeInterface|null
|
||||
*/
|
||||
protected function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get absolute path of node
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAbsolutePath()
|
||||
{
|
||||
return $this->getParent()->getAbsolutePath() . '/' . $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current node is writable if parent is writable
|
||||
*
|
||||
* @return bool TRUE if parent is writable
|
||||
*/
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->getParent()->isWritable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if node exists.
|
||||
* Returns TRUE if it is there, even if it is only a link.
|
||||
* Does not check the type!
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function exists()
|
||||
{
|
||||
if (@is_link($this->getAbsolutePath())) {
|
||||
return true;
|
||||
}
|
||||
return @file_exists($this->getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix permission if they are not equal to target permission
|
||||
*
|
||||
* @throws Exception
|
||||
* @return FlashMessage
|
||||
*/
|
||||
protected function fixPermission(): FlashMessage
|
||||
{
|
||||
if ($this->isPermissionCorrect()) {
|
||||
throw new Exception(
|
||||
'Permission on ' . $this->getAbsolutePath() . ' are already ok',
|
||||
1366744035
|
||||
);
|
||||
}
|
||||
$result = @chmod($this->getAbsolutePath(), octdec($this->getTargetPermission()));
|
||||
if ($result === true) {
|
||||
return new FlashMessage(
|
||||
'',
|
||||
'Fixed permission on ' . $this->getRelativePathBelowSiteRoot() . '.'
|
||||
);
|
||||
}
|
||||
return new FlashMessage(
|
||||
'Permissions could not be changed to ' . $this->getTargetPermission()
|
||||
. '. This only is a problem if files and folders within this node cannot be written.',
|
||||
'Permission change on ' . $this->getRelativePathBelowSiteRoot() . ' not successful',
|
||||
FlashMessage::NOTICE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current permission are identical to target permission
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isPermissionCorrect()
|
||||
{
|
||||
if ($this->isWindowsOs()) {
|
||||
return true;
|
||||
}
|
||||
if ($this->getCurrentPermission() === $this->getTargetPermission()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current permission of node
|
||||
*
|
||||
* @return string, eg. 2775 for dirs, 0664 for files
|
||||
*/
|
||||
protected function getCurrentPermission()
|
||||
{
|
||||
$permissions = decoct(fileperms($this->getAbsolutePath()));
|
||||
return substr($permissions, -4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if OS is windows
|
||||
*
|
||||
* @return bool TRUE on windows
|
||||
*/
|
||||
protected function isWindowsOs()
|
||||
{
|
||||
return Environment::isWindows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cut off public web path from given path
|
||||
*
|
||||
* @param string $path Given path
|
||||
* @return string Relative path, but beginning with /
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
protected function getRelativePathBelowSiteRoot($path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = $this->getAbsolutePath();
|
||||
}
|
||||
$publicPath = Environment::getPublicPath();
|
||||
if (strpos($path, $publicPath, 0) !== 0) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Public path is not first part of given path',
|
||||
1366398198
|
||||
);
|
||||
}
|
||||
$relativePath = substr($path, strlen($publicPath), strlen($path));
|
||||
// Add a forward slash again, so we don't end up with an empty string
|
||||
if ($relativePath === '') {
|
||||
$relativePath = '/';
|
||||
}
|
||||
return $relativePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Factory returns default folder structure object hierarchy
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class DefaultFactory
|
||||
{
|
||||
/**
|
||||
* Get default structure object hierarchy
|
||||
*
|
||||
* @return StructureFacadeInterface
|
||||
*/
|
||||
public function getStructure()
|
||||
{
|
||||
$rootNode = new RootNode($this->getDefaultStructureDefinition(), null);
|
||||
$structureFacade = new StructureFacade($rootNode);
|
||||
return $structureFacade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default definition of folder and file structure with dynamic
|
||||
* permission settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultStructureDefinition(): array
|
||||
{
|
||||
$filePermission = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'];
|
||||
$directoryPermission = $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'];
|
||||
|
||||
$structure = [
|
||||
// Note that root node has no trailing slash like all others
|
||||
'name' => Environment::getPublicPath(),
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'typo3temp',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'index.html',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContent' => '',
|
||||
],
|
||||
[
|
||||
'name' => 'assets',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'compressed',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission
|
||||
],
|
||||
[
|
||||
'name' => 'css',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission
|
||||
],
|
||||
[
|
||||
'name' => 'js',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission
|
||||
],
|
||||
[
|
||||
'name' => 'images',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission
|
||||
],
|
||||
[
|
||||
'name' => '_processed_',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'var',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => '.htaccess',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContentFile' => Environment::getFrameworkBasePath() . '/install/Resources/Private/FolderStructureTemplateFiles/typo3temp-var-htaccess',
|
||||
],
|
||||
[
|
||||
'name' => 'charset',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
],
|
||||
[
|
||||
'name' => 'cache',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
],
|
||||
[
|
||||
'name' => 'lock',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'typo3conf',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'ext',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
],
|
||||
[
|
||||
'name' => 'l10n',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']) ? rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') : 'fileadmin',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => '.htaccess',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContentFile' => Environment::getFrameworkBasePath() . '/install/Resources/Private/FolderStructureTemplateFiles/resources-root-htaccess',
|
||||
],
|
||||
[
|
||||
'name' => '_temp_',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => '.htaccess',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContentFile' => Environment::getFrameworkBasePath() . '/install/Resources/Private/FolderStructureTemplateFiles/fileadmin-temp-htaccess',
|
||||
],
|
||||
[
|
||||
'name' => 'index.html',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContentFile' => Environment::getFrameworkBasePath() . '/install/Resources/Private/FolderStructureTemplateFiles/fileadmin-temp-index.html',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'user_upload',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => '_temp_',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'index.html',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContent' => '',
|
||||
],
|
||||
[
|
||||
'name' => 'importexport',
|
||||
'type' => DirectoryNode::class,
|
||||
'targetPermission' => $directoryPermission,
|
||||
'children' => [
|
||||
[
|
||||
'name' => '.htaccess',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContentFile' => Environment::getFrameworkBasePath() . '/install/Resources/Private/FolderStructureTemplateFiles/fileadmin-user_upload-temp-importexport-htaccess',
|
||||
],
|
||||
[
|
||||
'name' => 'index.html',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContentFile' => Environment::getFrameworkBasePath() . '/install/Resources/Private/FolderStructureTemplateFiles/fileadmin-temp-index.html',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'index.html',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContent' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Have a default .htaccess if running apache web server or a default web.config if running IIS
|
||||
if (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === 0) {
|
||||
$structure['children'][] = [
|
||||
'name' => '.htaccess',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContentFile' => Environment::getPublicPath() . '/typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/root-htaccess',
|
||||
];
|
||||
} elseif (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === 0) {
|
||||
$structure['children'][] = [
|
||||
'name' => 'web.config',
|
||||
'type' => FileNode::class,
|
||||
'targetPermission' => $filePermission,
|
||||
'targetContentFile' => Environment::getPublicPath() . '/typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/root-web-config',
|
||||
];
|
||||
}
|
||||
|
||||
return $structure;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessage;
|
||||
|
||||
/**
|
||||
* Service class to check the default folder permissions
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class DefaultPermissionsCheck
|
||||
{
|
||||
/**
|
||||
* @var array Recommended values for a secure production site
|
||||
*
|
||||
* These are not the default settings (which are 0664/2775), because they might not work on every installation.
|
||||
* For security reasons these are the recommended values nevertheless (no world-readable files).
|
||||
* It's up to the admins to decide if these recommended secure values can be applied to their installation.
|
||||
*/
|
||||
protected $recommended = [
|
||||
'fileCreateMask' => '0660',
|
||||
'folderCreateMask' => '2770',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array Verbose names of the settings
|
||||
*/
|
||||
protected $names = [
|
||||
'fileCreateMask' => 'Default File permissions',
|
||||
'folderCreateMask' => 'Default Directory permissions',
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks a BE/*mask setting for it's security
|
||||
*
|
||||
* If it permits world writing: Error
|
||||
* If it permits world reading: Warning
|
||||
* If it permits group writing: Notice
|
||||
* If it permits group reading: Notice
|
||||
* If it permits only user read/write: Ok
|
||||
*
|
||||
* @param string $which fileCreateMask or folderCreateMask
|
||||
* @return FlashMessage
|
||||
*/
|
||||
public function getMaskStatus($which): FlashMessage
|
||||
{
|
||||
$octal = '0' . $GLOBALS['TYPO3_CONF_VARS']['SYS'][$which];
|
||||
$dec = octdec($octal);
|
||||
$perms = [
|
||||
'ox' => ($dec & 001) == 001,
|
||||
'ow' => ($dec & 002) == 002,
|
||||
'or' => ($dec & 004) == 004,
|
||||
'gx' => ($dec & 010) == 010,
|
||||
'gw' => ($dec & 020) == 020,
|
||||
'gr' => ($dec & 040) == 040,
|
||||
'ux' => ($dec & 0100) == 0100,
|
||||
'uw' => ($dec & 0200) == 0200,
|
||||
'ur' => ($dec & 0400) == 0400,
|
||||
'setgid' => ($dec & 02000) == 02000,
|
||||
];
|
||||
$extraMessage = '';
|
||||
$groupPermissions = false;
|
||||
if (!$perms['uw'] || !$perms['ur']) {
|
||||
$permissionStatus = FlashMessage::ERROR;
|
||||
$extraMessage = ' (not read or writable by the user)';
|
||||
} elseif ($perms['ow']) {
|
||||
if (Environment::isWindows()) {
|
||||
$permissionStatus = FlashMessage::INFO;
|
||||
$extraMessage = ' (writable by anyone on the server). This is the default behavior on a Windows system';
|
||||
} else {
|
||||
$permissionStatus = FlashMessage::ERROR;
|
||||
$extraMessage = ' (writable by anyone on the server)';
|
||||
}
|
||||
} elseif ($perms['or']) {
|
||||
$permissionStatus = FlashMessage::NOTICE;
|
||||
$extraMessage = ' (readable by anyone on the server). This is the default set by TYPO3 CMS to be as much compatible as possible but if your system allows, please consider to change rights';
|
||||
} elseif ($perms['gw']) {
|
||||
$permissionStatus = FlashMessage::OK;
|
||||
$extraMessage = ' (group writable)';
|
||||
$groupPermissions = true;
|
||||
} elseif ($perms['gr']) {
|
||||
$permissionStatus = FlashMessage::OK;
|
||||
$extraMessage = ' (group readable)';
|
||||
$groupPermissions = true;
|
||||
} else {
|
||||
$permissionStatus = FlashMessage::OK;
|
||||
}
|
||||
$message = 'Recommended: ' . $this->recommended[$which] . '.';
|
||||
$message .= ' Currently configured as ';
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['SYS'][$which] === $this->recommended[$which]) {
|
||||
$message .= 'recommended';
|
||||
} else {
|
||||
$message .= $GLOBALS['TYPO3_CONF_VARS']['SYS'][$which];
|
||||
}
|
||||
$message .= $extraMessage . '.';
|
||||
if ($groupPermissions) {
|
||||
$message .= ' This is fine as long as the web server\'s group only comprises trusted users.';
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'])) {
|
||||
$message .= ' Your site is configured (SYS/createGroup) to write as group \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] . '\'.';
|
||||
}
|
||||
}
|
||||
return new FlashMessage(
|
||||
$message,
|
||||
$this->names[$which] . ' (SYS/' . $which . ')',
|
||||
$permissionStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
|
||||
/**
|
||||
* A directory
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class DirectoryNode extends AbstractNode implements NodeInterface
|
||||
{
|
||||
/**
|
||||
* @var int|null Default for directories is octal 02775 == decimal 1533
|
||||
*/
|
||||
protected $targetPermission = '2775';
|
||||
|
||||
/**
|
||||
* Implement constructor
|
||||
*
|
||||
* @param array $structure Structure array
|
||||
* @param NodeInterface $parent Parent object
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $structure, NodeInterface $parent = null)
|
||||
{
|
||||
if ($parent === null) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Node must have parent',
|
||||
1366222203
|
||||
);
|
||||
}
|
||||
$this->parent = $parent;
|
||||
|
||||
// Ensure name is a single segment, but not a path like foo/bar or an absolute path /foo
|
||||
if (strstr($structure['name'], '/') !== false) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Directory name must not contain forward slash',
|
||||
1366226639
|
||||
);
|
||||
}
|
||||
$this->name = $structure['name'];
|
||||
|
||||
if (isset($structure['targetPermission'])) {
|
||||
$this->setTargetPermission($structure['targetPermission']);
|
||||
}
|
||||
|
||||
if (array_key_exists('children', $structure)) {
|
||||
$this->createChildren($structure['children']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get own status and status of child objects
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
$result = [];
|
||||
if (!$this->exists()) {
|
||||
$status = new FlashMessage(
|
||||
'The Install Tool can try to create it',
|
||||
'Directory ' . $this->getRelativePathBelowSiteRoot() . ' does not exist',
|
||||
FlashMessage::WARNING
|
||||
);
|
||||
$result[] = $status;
|
||||
} else {
|
||||
$result = $this->getSelfStatus();
|
||||
}
|
||||
$result = array_merge($result, $this->getChildrenStatus());
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test file and delete again if directory exists
|
||||
*
|
||||
* @return bool TRUE if test file creation was successful
|
||||
*/
|
||||
public function isWritable()
|
||||
{
|
||||
$result = true;
|
||||
if (!$this->exists()) {
|
||||
$result = false;
|
||||
} elseif (!$this->canFileBeCreated()) {
|
||||
$result = false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix structure
|
||||
*
|
||||
* If there is nothing to fix, returns an empty array
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
public function fix(): array
|
||||
{
|
||||
$result = $this->fixSelf();
|
||||
foreach ($this->children as $child) {
|
||||
/** @var NodeInterface $child */
|
||||
$result = array_merge($result, $child->fix());
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix this directory:
|
||||
*
|
||||
* - create with correct permissions if it was not existing
|
||||
* - if there is no "write" permissions, try to fix it
|
||||
* - leave it alone otherwise
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
protected function fixSelf()
|
||||
{
|
||||
$result = [];
|
||||
if (!$this->exists()) {
|
||||
$resultCreateDirectory = $this->createDirectory();
|
||||
$result[] = $resultCreateDirectory;
|
||||
if ($resultCreateDirectory->getSeverity() === FlashMessage::OK &&
|
||||
!$this->isPermissionCorrect()
|
||||
) {
|
||||
$result[] = $this->fixPermission();
|
||||
}
|
||||
} elseif (!$this->isWritable()) {
|
||||
// If directory is not writable, we might have permissions to fix that
|
||||
// Try it:
|
||||
$result[] = $this->fixPermission();
|
||||
} elseif (!$this->isDirectory()) {
|
||||
$fileType = @filetype($this->getAbsolutePath());
|
||||
if ($fileType) {
|
||||
$messageBody =
|
||||
'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a directory,' .
|
||||
' but is of type ' . $fileType . '. This cannot be fixed automatically. Please investigate.'
|
||||
;
|
||||
} else {
|
||||
$messageBody =
|
||||
'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a directory,' .
|
||||
' but is of unknown type, probably because an upper level directory does not exist. Please investigate.'
|
||||
;
|
||||
}
|
||||
$result[] = new FlashMessage(
|
||||
$messageBody,
|
||||
'Path ' . $this->getRelativePathBelowSiteRoot() . ' is not a directory',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create directory if not exists
|
||||
*
|
||||
* @throws Exception
|
||||
* @return FlashMessage
|
||||
*/
|
||||
protected function createDirectory(): FlashMessage
|
||||
{
|
||||
if ($this->exists()) {
|
||||
throw new Exception(
|
||||
'Directory ' . $this->getAbsolutePath() . ' already exists',
|
||||
1366740091
|
||||
);
|
||||
}
|
||||
$result = @mkdir($this->getAbsolutePath());
|
||||
if ($result === true) {
|
||||
return new FlashMessage(
|
||||
'',
|
||||
'Directory ' . $this->getRelativePathBelowSiteRoot() . ' successfully created.'
|
||||
);
|
||||
}
|
||||
return new FlashMessage(
|
||||
'The target directory could not be created. There is probably a'
|
||||
. ' group or owner permission problem on the parent directory.',
|
||||
'Directory ' . $this->getRelativePathBelowSiteRoot() . ' not created!',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status of directory - used in root and directory node
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
protected function getSelfStatus(): array
|
||||
{
|
||||
$result = [];
|
||||
if (!$this->isDirectory()) {
|
||||
$result[] = new FlashMessage(
|
||||
'Directory ' . $this->getRelativePathBelowSiteRoot() . ' should be a directory,'
|
||||
. ' but is of type ' . filetype($this->getAbsolutePath()),
|
||||
$this->getRelativePathBelowSiteRoot() . ' is not a directory',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
} elseif (!$this->isWritable()) {
|
||||
$result[] = new FlashMessage(
|
||||
'Path ' . $this->getAbsolutePath() . ' exists, but no file underneath it'
|
||||
. ' can be created.',
|
||||
'Directory ' . $this->getRelativePathBelowSiteRoot() . ' is not writable',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
} elseif (!$this->isPermissionCorrect()) {
|
||||
$result[] = new FlashMessage(
|
||||
'Default configured permissions are ' . $this->getTargetPermission()
|
||||
. ' but current permissions are ' . $this->getCurrentPermission(),
|
||||
'Directory ' . $this->getRelativePathBelowSiteRoot() . ' permissions mismatch',
|
||||
FlashMessage::NOTICE
|
||||
);
|
||||
} else {
|
||||
$result[] = new FlashMessage(
|
||||
'Is a directory with the configured permissions of ' . $this->getTargetPermission(),
|
||||
'Directory ' . $this->getRelativePathBelowSiteRoot()
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status of children
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
protected function getChildrenStatus(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($this->children as $child) {
|
||||
/** @var NodeInterface $child */
|
||||
$result = array_merge($result, $child->getStatus());
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test file and delete again - helper for isWritable
|
||||
*
|
||||
* @return bool TRUE if test file creation was successful
|
||||
*/
|
||||
protected function canFileBeCreated()
|
||||
{
|
||||
$testFileName = StringUtility::getUniqueId('installToolTest_');
|
||||
$result = @touch($this->getAbsolutePath() . '/' . $testFileName);
|
||||
if ($result === true) {
|
||||
unlink($this->getAbsolutePath() . '/' . $testFileName);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if not is a directory
|
||||
*
|
||||
* @return bool True if node is a directory
|
||||
*/
|
||||
protected function isDirectory()
|
||||
{
|
||||
$path = $this->getAbsolutePath();
|
||||
return !@is_link($path) && @is_dir($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create children nodes - done in directory and root node
|
||||
*
|
||||
* @param array $structure Array of children
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
protected function createChildren(array $structure)
|
||||
{
|
||||
foreach ($structure as $child) {
|
||||
if (!array_key_exists('type', $child)) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Child must have type',
|
||||
1366222204
|
||||
);
|
||||
}
|
||||
if (!array_key_exists('name', $child)) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Child must have name',
|
||||
1366222205
|
||||
);
|
||||
}
|
||||
$name = $child['name'];
|
||||
foreach ($this->children as $existingChild) {
|
||||
/** @var NodeInterface $existingChild */
|
||||
if ($existingChild->getName() === $name) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Child name must be unique',
|
||||
1366222206
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->children[] = new $child['type']($child, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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 folder structure exception
|
||||
*/
|
||||
class Exception extends \TYPO3\CMS\Install\Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure\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 invalid argument exception
|
||||
*/
|
||||
class InvalidArgumentException extends \TYPO3\CMS\Install\FolderStructure\Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* A root node exception
|
||||
*/
|
||||
class RootNodeException extends \TYPO3\CMS\Install\FolderStructure\Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessage;
|
||||
|
||||
/**
|
||||
* A file
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class FileNode extends AbstractNode implements NodeInterface
|
||||
{
|
||||
/**
|
||||
* @var int|null Default for files is octal 0664 == decimal 436
|
||||
*/
|
||||
protected $targetPermission = '0664';
|
||||
|
||||
/**
|
||||
* @var string|null Target content of file. If NULL, target content is ignored
|
||||
*/
|
||||
protected $targetContent;
|
||||
|
||||
/**
|
||||
* Implement constructor
|
||||
*
|
||||
* @param array $structure Structure array
|
||||
* @param NodeInterface $parent Parent object
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $structure, NodeInterface $parent = null)
|
||||
{
|
||||
if ($parent === null) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'File node must have parent',
|
||||
1366927513
|
||||
);
|
||||
}
|
||||
$this->parent = $parent;
|
||||
|
||||
// Ensure name is a single segment, but not a path like foo/bar or an absolute path /foo
|
||||
if (strstr($structure['name'], '/') !== false) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'File name must not contain forward slash',
|
||||
1366222207
|
||||
);
|
||||
}
|
||||
$this->name = $structure['name'];
|
||||
|
||||
if (isset($structure['targetPermission'])) {
|
||||
$this->setTargetPermission($structure['targetPermission']);
|
||||
}
|
||||
|
||||
if (isset($structure['targetContent']) && isset($structure['targetContentFile'])) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Either targetContent or targetContentFile can be set, but not both',
|
||||
1380364361
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($structure['targetContent'])) {
|
||||
$this->targetContent = $structure['targetContent'];
|
||||
}
|
||||
if (isset($structure['targetContentFile'])) {
|
||||
if (!is_readable($structure['targetContentFile'])) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'targetContentFile ' . $structure['targetContentFile'] . ' does not exist or is not readable',
|
||||
1380364362
|
||||
);
|
||||
}
|
||||
$this->targetContent = file_get_contents($structure['targetContentFile']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get own status
|
||||
* Returns warning if file not exists
|
||||
* Returns error if file exists but content is not as expected (can / shouldn't be fixed)
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
$result = [];
|
||||
if (!$this->exists()) {
|
||||
$result[] = new FlashMessage(
|
||||
'By using "Try to fix errors" we can try to create it',
|
||||
'File ' . $this->getRelativePathBelowSiteRoot() . ' does not exist',
|
||||
FlashMessage::WARNING
|
||||
);
|
||||
} else {
|
||||
$result = $this->getSelfStatus();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix structure
|
||||
*
|
||||
* If there is nothing to fix, returns an empty array
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
public function fix(): array
|
||||
{
|
||||
$result = $this->fixSelf();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix this node: create if not there, fix permissions
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
protected function fixSelf(): array
|
||||
{
|
||||
$result = [];
|
||||
if (!$this->exists()) {
|
||||
$resultCreateFile = $this->createFile();
|
||||
$result[] = $resultCreateFile;
|
||||
if ($resultCreateFile->getSeverity() === FlashMessage::OK
|
||||
&& $this->targetContent !== null
|
||||
) {
|
||||
$result[] = $this->setContent();
|
||||
if (!$this->isPermissionCorrect()) {
|
||||
$result[] = $this->fixPermission();
|
||||
}
|
||||
}
|
||||
} elseif (!$this->isFile()) {
|
||||
$fileType = @filetype($this->getAbsolutePath());
|
||||
if ($fileType) {
|
||||
$messageBody =
|
||||
'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' .
|
||||
' but is of type ' . $fileType . '. This cannot be fixed automatically. Please investigate.'
|
||||
;
|
||||
} else {
|
||||
$messageBody =
|
||||
'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' .
|
||||
' but is of unknown type, probably because an upper level directory does not exist. Please investigate.'
|
||||
;
|
||||
}
|
||||
$result[] = new FlashMessage(
|
||||
$messageBody,
|
||||
'Path ' . $this->getRelativePathBelowSiteRoot() . ' is not a file',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
} elseif (!$this->isPermissionCorrect()) {
|
||||
$result[] = $this->fixPermission();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create file if not exists
|
||||
*
|
||||
* @throws Exception
|
||||
* @return FlashMessage
|
||||
*/
|
||||
protected function createFile(): FlashMessage
|
||||
{
|
||||
if ($this->exists()) {
|
||||
throw new Exception(
|
||||
'File ' . $this->getRelativePathBelowSiteRoot() . ' already exists',
|
||||
1367048077
|
||||
);
|
||||
}
|
||||
$result = @touch($this->getAbsolutePath());
|
||||
if ($result === true) {
|
||||
return new FlashMessage(
|
||||
'',
|
||||
'File ' . $this->getRelativePathBelowSiteRoot() . ' successfully created.'
|
||||
);
|
||||
}
|
||||
return new FlashMessage(
|
||||
'The target file could not be created. There is probably a'
|
||||
. ' group or owner permission problem on the parent directory.',
|
||||
'File ' . $this->getRelativePathBelowSiteRoot() . ' not created!',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status of file
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
protected function getSelfStatus(): array
|
||||
{
|
||||
$result = [];
|
||||
if (!$this->isFile()) {
|
||||
$result[] = new FlashMessage(
|
||||
'Path ' . $this->getAbsolutePath() . ' should be a file,'
|
||||
. ' but is of type ' . filetype($this->getAbsolutePath()),
|
||||
$this->getRelativePathBelowSiteRoot() . ' is not a file',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
} elseif (!$this->isWritable()) {
|
||||
$result[] = new FlashMessage(
|
||||
'File ' . $this->getRelativePathBelowSiteRoot() . ' exists, but is not writable.',
|
||||
'File ' . $this->getRelativePathBelowSiteRoot() . ' is not writable',
|
||||
FlashMessage::NOTICE
|
||||
);
|
||||
} elseif (!$this->isPermissionCorrect()) {
|
||||
$result[] = new FlashMessage(
|
||||
'Default configured permissions are ' . $this->getTargetPermission()
|
||||
. ' but file permissions are ' . $this->getCurrentPermission(),
|
||||
'File ' . $this->getRelativePathBelowSiteRoot() . ' permissions mismatch',
|
||||
FlashMessage::NOTICE
|
||||
);
|
||||
}
|
||||
if ($this->isFile() && !$this->isContentCorrect()) {
|
||||
$result[] = new FlashMessage(
|
||||
'File content is not identical to default content. This file may have been changed manually.'
|
||||
. ' The Install Tool will not overwrite the current version!',
|
||||
'File ' . $this->getRelativePathBelowSiteRoot() . ' content differs',
|
||||
FlashMessage::NOTICE
|
||||
);
|
||||
} else {
|
||||
$result[] = new FlashMessage(
|
||||
'Is a file with the default content and configured permissions of ' . $this->getTargetPermission(),
|
||||
'File ' . $this->getRelativePathBelowSiteRoot()
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare current file content with target file content
|
||||
*
|
||||
* @throws Exception If file does not exist
|
||||
* @return bool TRUE if current and target file content are identical
|
||||
*/
|
||||
protected function isContentCorrect()
|
||||
{
|
||||
$absolutePath = $this->getAbsolutePath();
|
||||
if (is_link($absolutePath) || !is_file($absolutePath)) {
|
||||
throw new Exception(
|
||||
'File ' . $absolutePath . ' must exist',
|
||||
1367056363
|
||||
);
|
||||
}
|
||||
$result = false;
|
||||
if ($this->targetContent === null) {
|
||||
$result = true;
|
||||
} else {
|
||||
$targetContentHash = md5($this->targetContent);
|
||||
$currentContentHash = md5(file_get_contents($absolutePath));
|
||||
if ($targetContentHash === $currentContentHash) {
|
||||
$result = true;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets content of file to target content
|
||||
*
|
||||
* @throws Exception If file does not exist
|
||||
* @return FlashMessage
|
||||
*/
|
||||
protected function setContent(): FlashMessage
|
||||
{
|
||||
$absolutePath = $this->getAbsolutePath();
|
||||
if (is_link($absolutePath) || !is_file($absolutePath)) {
|
||||
throw new Exception(
|
||||
'File ' . $absolutePath . ' must exist',
|
||||
1367060201
|
||||
);
|
||||
}
|
||||
if ($this->targetContent === null) {
|
||||
throw new Exception(
|
||||
'Target content not defined for ' . $absolutePath,
|
||||
1367060202
|
||||
);
|
||||
}
|
||||
$result = @file_put_contents($absolutePath, $this->targetContent);
|
||||
if ($result !== false) {
|
||||
return new FlashMessage(
|
||||
'',
|
||||
'Set content to ' . $this->getRelativePathBelowSiteRoot()
|
||||
);
|
||||
}
|
||||
return new FlashMessage(
|
||||
'Setting content of the file failed for unknown reasons.',
|
||||
'Setting content to ' . $this->getRelativePathBelowSiteRoot() . ' failed',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if not is a file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isFile()
|
||||
{
|
||||
$path = $this->getAbsolutePath();
|
||||
return !is_link($path) && is_file($path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessage;
|
||||
|
||||
/**
|
||||
* A link
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class LinkNode extends AbstractNode implements NodeInterface
|
||||
{
|
||||
/**
|
||||
* @var string Optional link target
|
||||
*/
|
||||
protected $target = '';
|
||||
|
||||
/**
|
||||
* Implement constructor
|
||||
*
|
||||
* @param array $structure Structure array
|
||||
* @param NodeInterface $parent Parent object
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $structure, NodeInterface $parent = null)
|
||||
{
|
||||
if ($parent === null) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Link node must have parent',
|
||||
1380485700
|
||||
);
|
||||
}
|
||||
$this->parent = $parent;
|
||||
|
||||
// Ensure name is a single segment, but not a path like foo/bar or an absolute path /foo
|
||||
if (strstr($structure['name'], '/') !== false) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'File name must not contain forward slash',
|
||||
1380546061
|
||||
);
|
||||
}
|
||||
$this->name = $structure['name'];
|
||||
|
||||
if (isset($structure['target']) && $structure['target'] !== '') {
|
||||
$this->target = $structure['target'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get own status
|
||||
* Returns information status if running on Windows
|
||||
* Returns OK status if is link and possible target is correct
|
||||
* Else returns error (not fixable)
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
if ($this->isWindowsOs()) {
|
||||
return [
|
||||
new FlashMessage(
|
||||
'This node is not handled for Windows OS and should be checked manually.',
|
||||
$this->getRelativePathBelowSiteRoot() . ' should be a link, but this support is incomplete for Windows.',
|
||||
FlashMessage::INFO
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (!$this->exists()) {
|
||||
return [
|
||||
new FlashMessage(
|
||||
'Links cannot be fixed by this system',
|
||||
$this->getRelativePathBelowSiteRoot() . ' should be a link, but it does not exist',
|
||||
FlashMessage::ERROR
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (!$this->isLink()) {
|
||||
$type = @filetype($this->getAbsolutePath());
|
||||
if ($type) {
|
||||
$messageBody =
|
||||
'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a link,' .
|
||||
' but is of type ' . $type . '. This cannot be fixed automatically. Please investigate.'
|
||||
;
|
||||
} else {
|
||||
$messageBody =
|
||||
'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' .
|
||||
' but is of unknown type, probably because an upper level directory does not exist. Please investigate.'
|
||||
;
|
||||
}
|
||||
return [
|
||||
new FlashMessage(
|
||||
$messageBody,
|
||||
'Path ' . $this->getRelativePathBelowSiteRoot() . ' is not a link',
|
||||
FlashMessage::WARNING
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (!$this->isTargetCorrect()) {
|
||||
return [
|
||||
new FlashMessage(
|
||||
'Link target should be ' . $this->getTarget() . ' but is ' . $this->getCurrentTarget(),
|
||||
$this->getRelativePathBelowSiteRoot() . ' is a link, but link target is not as specified',
|
||||
FlashMessage::ERROR
|
||||
),
|
||||
];
|
||||
}
|
||||
$message = 'Is a link';
|
||||
if ($this->getTarget() !== '') {
|
||||
$message .= ' and correctly points to target ' . $this->getTarget();
|
||||
}
|
||||
return [
|
||||
new FlashMessage(
|
||||
$message,
|
||||
$this->getRelativePathBelowSiteRoot()
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix structure
|
||||
*
|
||||
* If there is nothing to fix, returns an empty array
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
public function fix(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get link target
|
||||
*
|
||||
* @return string Link target
|
||||
*/
|
||||
protected function getTarget()
|
||||
{
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out if node is a link
|
||||
*
|
||||
* @throws Exception\InvalidArgumentException
|
||||
* @return bool TRUE if node is a link
|
||||
*/
|
||||
protected function isLink()
|
||||
{
|
||||
if (!$this->exists()) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Link does not exist',
|
||||
1380556246
|
||||
);
|
||||
}
|
||||
return @is_link($this->getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the real link target is identical to given target
|
||||
*
|
||||
* @throws Exception\InvalidArgumentException
|
||||
* @return bool TRUE if target is correct
|
||||
*/
|
||||
protected function isTargetCorrect()
|
||||
{
|
||||
if (!$this->exists()) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Link does not exist',
|
||||
1380556245
|
||||
);
|
||||
}
|
||||
if (!$this->isLink()) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Node is not a link',
|
||||
1380556247
|
||||
);
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$expectedTarget = $this->getTarget();
|
||||
if (empty($expectedTarget)) {
|
||||
$result = true;
|
||||
} else {
|
||||
$actualTarget = $this->getCurrentTarget();
|
||||
if ($expectedTarget === rtrim($actualTarget, '/')) {
|
||||
$result = true;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current target of link
|
||||
*
|
||||
* @return string target
|
||||
*/
|
||||
protected function getCurrentTarget()
|
||||
{
|
||||
return readlink($this->getAbsolutePath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessage;
|
||||
|
||||
/**
|
||||
* Interface for structure nodes root, link, file, ...
|
||||
*/
|
||||
interface NodeInterface
|
||||
{
|
||||
/**
|
||||
* Constructor gets structure and parent object defaulting to NULL
|
||||
*
|
||||
* @param array $structure Structure
|
||||
* @param NodeInterface $parent Parent
|
||||
*/
|
||||
public function __construct(array $structure, NodeInterface $parent = null);
|
||||
|
||||
/**
|
||||
* Get node name
|
||||
*
|
||||
* @return string Node name
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Get absolute path of node
|
||||
*
|
||||
* @return string Absolute path
|
||||
*/
|
||||
public function getAbsolutePath();
|
||||
|
||||
/**
|
||||
* Get the status of the object tree, recursive for directory and root node
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
public function getStatus(): array;
|
||||
|
||||
/**
|
||||
* Check if node is writable - can be created and permission can be fixed
|
||||
*
|
||||
* @return bool TRUE if node is writable
|
||||
*/
|
||||
public function isWritable();
|
||||
|
||||
/**
|
||||
* Fix structure
|
||||
*
|
||||
* If there is nothing to fix, returns an empty array
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
public function fix(): array;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessage;
|
||||
|
||||
/**
|
||||
* Root node of structure
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class RootNode extends DirectoryNode implements RootNodeInterface
|
||||
{
|
||||
/**
|
||||
* Implement constructor
|
||||
*
|
||||
* @param array $structure Given structure
|
||||
* @param NodeInterface $parent Must be NULL for RootNode
|
||||
* @throws Exception\RootNodeException
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $structure, NodeInterface $parent = null)
|
||||
{
|
||||
if ($parent !== null) {
|
||||
throw new Exception\RootNodeException(
|
||||
'Root node must not have parent',
|
||||
1366140117
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($structure['name'])
|
||||
|| ($this->isWindowsOs() && substr($structure['name'], 1, 2) !== ':/')
|
||||
|| (!$this->isWindowsOs() && $structure['name'][0] !== '/')
|
||||
) {
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'Root node expects absolute path as name',
|
||||
1366141329
|
||||
);
|
||||
}
|
||||
$this->name = $structure['name'];
|
||||
|
||||
if (isset($structure['targetPermission'])) {
|
||||
$this->setTargetPermission($structure['targetPermission']);
|
||||
}
|
||||
|
||||
if (array_key_exists('children', $structure)) {
|
||||
$this->createChildren($structure['children']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get own status and status of child objects - Root node gives error status if not exists
|
||||
*
|
||||
* @return FlashMessage[]
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
$result = [];
|
||||
if (!$this->exists()) {
|
||||
$result[] = new FlashMessage(
|
||||
'',
|
||||
$this->getAbsolutePath() . ' does not exist',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
} else {
|
||||
$result = $this->getSelfStatus();
|
||||
}
|
||||
$result = array_merge($result, $this->getChildrenStatus());
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Root node does not call parent, but returns own name only
|
||||
*
|
||||
* @return string Absolute path
|
||||
*/
|
||||
public function getAbsolutePath()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface implemented by root node
|
||||
*/
|
||||
interface RootNodeInterface extends NodeInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessageQueue;
|
||||
|
||||
/**
|
||||
* Structure facade, a facade class in front of root node.
|
||||
* This is the main API interface to the node structure and should
|
||||
* be the only class used from outside.
|
||||
*/
|
||||
class StructureFacade implements StructureFacadeInterface
|
||||
{
|
||||
/**
|
||||
* @var RootNodeInterface The structure to work on
|
||||
*/
|
||||
protected $structure;
|
||||
|
||||
/**
|
||||
* Constructor sets structure to work on
|
||||
*
|
||||
* @param RootNodeInterface $structure
|
||||
*/
|
||||
public function __construct(RootNodeInterface $structure)
|
||||
{
|
||||
$this->structure = $structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status of node tree
|
||||
*
|
||||
* @return FlashMessageQueue
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue
|
||||
{
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
foreach ($this->structure->getStatus() as $message) {
|
||||
$messageQueue->enqueue($message);
|
||||
}
|
||||
return $messageQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix structure
|
||||
*
|
||||
* @return FlashMessageQueue
|
||||
*/
|
||||
public function fix(): FlashMessageQueue
|
||||
{
|
||||
$messageQueue = new FlashMessageQueue('install');
|
||||
foreach ($this->structure->fix() as $message) {
|
||||
$messageQueue->enqueue($message);
|
||||
}
|
||||
return $messageQueue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\FolderStructure;
|
||||
|
||||
/*
|
||||
* 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\Messaging\FlashMessageQueue;
|
||||
|
||||
/**
|
||||
* Interface of structure facade, a facade class in front of root node
|
||||
*/
|
||||
interface StructureFacadeInterface
|
||||
{
|
||||
/**
|
||||
* Constructor gets structure to work on
|
||||
*
|
||||
* @param RootNodeInterface $structure
|
||||
*/
|
||||
public function __construct(RootNodeInterface $structure);
|
||||
|
||||
/**
|
||||
* Get status of node tree
|
||||
*
|
||||
* @return FlashMessageQueue
|
||||
*/
|
||||
public function getStatus(): FlashMessageQueue;
|
||||
|
||||
/**
|
||||
* Fix structure
|
||||
*
|
||||
* @return FlashMessageQueue
|
||||
*/
|
||||
public function fix(): FlashMessageQueue;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Http;
|
||||
|
||||
/*
|
||||
* 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 Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\DateTimeAspect;
|
||||
use TYPO3\CMS\Core\Context\UserAspect;
|
||||
use TYPO3\CMS\Core\Context\VisibilityAspect;
|
||||
use TYPO3\CMS\Core\Context\WorkspaceAspect;
|
||||
use TYPO3\CMS\Core\Http\AbstractApplication;
|
||||
use TYPO3\CMS\Core\Http\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Entry point for the TYPO3 Install Tool
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class Application extends AbstractApplication
|
||||
{
|
||||
/**
|
||||
* All available request handlers that can handle an install tool request
|
||||
* @var array
|
||||
*/
|
||||
protected $availableRequestHandlers = [];
|
||||
|
||||
/**
|
||||
* Construct Application
|
||||
*
|
||||
* @param RequestHandlerInterface $requestHandler
|
||||
* @param RequestHandlerInterface $installerRequestHandler
|
||||
*/
|
||||
public function __construct(
|
||||
RequestHandlerInterface $requestHandler,
|
||||
RequestHandlerInterface $installerRequestHandler
|
||||
) {
|
||||
$this->availableRequestHandlers = [
|
||||
$requestHandler,
|
||||
$installerRequestHandler
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->initializeContext();
|
||||
foreach ($this->availableRequestHandlers as $handler) {
|
||||
if ($handler->canHandleRequest($request)) {
|
||||
return $handler->handle($request)
|
||||
->withHeader('X-Frame-Options', 'SAMEORIGIN');
|
||||
}
|
||||
}
|
||||
throw new \TYPO3\CMS\Core\Exception('No suitable request handler found.', 1518448686);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Context used for accessing data and finding out the current state of the application
|
||||
* Will be moved to a DI-like concept once introduced, for now, this is a singleton
|
||||
*/
|
||||
protected function initializeContext()
|
||||
{
|
||||
GeneralUtility::makeInstance(Context::class, [
|
||||
'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
|
||||
'visibility' => new VisibilityAspect(true, true, true),
|
||||
'workspace' => new WorkspaceAspect(0),
|
||||
'backend.user' => new UserAspect(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Http;
|
||||
|
||||
/*
|
||||
* 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 Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Http\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Install\Controller\InstallerController;
|
||||
use TYPO3\CMS\Install\Service\EnableFileService;
|
||||
use TYPO3\CMS\Install\Service\SessionService;
|
||||
|
||||
/**
|
||||
* Request handler to walk through the web installation process of TYPO3
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class InstallerRequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Handles an Install Tool request when nothing is there
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return $this->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an Install Tool request when nothing is there
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$controller = new InstallerController();
|
||||
$actionName = $request->getParsedBody()['install']['action'] ?? $request->getQueryParams()['install']['action'] ?? 'init';
|
||||
$action = $actionName . 'Action';
|
||||
|
||||
if ($actionName === 'init' || $actionName === 'mainLayout') {
|
||||
$response = $controller->$action();
|
||||
} elseif ($actionName === 'checkInstallerAvailable') {
|
||||
$response = new JsonResponse([
|
||||
'success' => $this->isInstallerAvailable(),
|
||||
]);
|
||||
} elseif ($actionName === 'showInstallerNotAvailable') {
|
||||
$response = $controller->showInstallerNotAvailableAction();
|
||||
} elseif ($actionName === 'checkEnvironmentAndFolders'
|
||||
|| $actionName === 'showEnvironmentAndFolders'
|
||||
|| $actionName === 'executeEnvironmentAndFolders'
|
||||
) {
|
||||
$this->throwIfInstallerIsNotAvailable();
|
||||
$response = $controller->$action($request);
|
||||
} else {
|
||||
$this->throwIfInstallerIsNotAvailable();
|
||||
// With main folder layout available, sessions can be handled
|
||||
$session = new SessionService();
|
||||
if (!$session->hasSession()) {
|
||||
$session->startSession();
|
||||
}
|
||||
if ($session->isExpired()) {
|
||||
$session->refreshSession();
|
||||
}
|
||||
$postValues = $request->getParsedBody()['install'];
|
||||
$sessionTokenOk = false;
|
||||
if (empty($postValues)) {
|
||||
// No post data is there, no token check necessary
|
||||
$sessionTokenOk = true;
|
||||
}
|
||||
if (isset($postValues['token'])) {
|
||||
// A token must be given as soon as there is POST data
|
||||
$formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
|
||||
if ($actionName === '') {
|
||||
throw new \RuntimeException('No POST action given for token check', 1505647681);
|
||||
}
|
||||
$sessionTokenOk = $formProtection->validateToken($postValues['token'], 'installTool', $actionName);
|
||||
}
|
||||
if (!$sessionTokenOk) {
|
||||
$session->resetSession();
|
||||
$session->startSession();
|
||||
throw new \RuntimeException('Invalid session token', 1505647737);
|
||||
}
|
||||
|
||||
if (!method_exists($controller, $action)) {
|
||||
// Sanitize action method, preventing injecting whatever method name
|
||||
throw new \RuntimeException(
|
||||
'Unknown action method ' . $action . ' in controller InstallerController',
|
||||
1505687700
|
||||
);
|
||||
}
|
||||
|
||||
$response = $controller->$action($request);
|
||||
|
||||
if ($actionName === 'executeDefaultConfiguration') {
|
||||
// Executing last step cleans session
|
||||
$session->destroySession();
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* First installation is in progress, if LocalConfiguration does not exist,
|
||||
* or if FIRST_INSTALL file exists.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return bool Returns always TRUE
|
||||
*/
|
||||
public function canHandleRequest(ServerRequestInterface $request): bool
|
||||
{
|
||||
$localConfigurationFileLocation = (new ConfigurationManager())->getLocalConfigurationFileLocation();
|
||||
return !@is_file($localConfigurationFileLocation) || EnableFileService::isFirstInstallAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the priority - how eager the handler is to actually handle the request.
|
||||
*
|
||||
* @return int The priority of the request handler.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException If installer is not available due to missing FIRST_INSTALL
|
||||
*/
|
||||
protected function throwIfInstallerIsNotAvailable()
|
||||
{
|
||||
if (!$this->isInstallerAvailable()) {
|
||||
throw new \RuntimeException(
|
||||
'Installer not available',
|
||||
1505637427
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool TRUE if FIRST_INSTALL file exists
|
||||
*/
|
||||
protected function isInstallerAvailable(): bool
|
||||
{
|
||||
if (EnableFileService::isFirstInstallAllowed()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Http;
|
||||
|
||||
/*
|
||||
* 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 Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\Configuration\Features;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Http\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Http\Security\ReferrerEnforcer;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Package\PackageInterface;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Authentication\AuthenticationService;
|
||||
use TYPO3\CMS\Install\Controller\AbstractController;
|
||||
use TYPO3\CMS\Install\Controller\EnvironmentController;
|
||||
use TYPO3\CMS\Install\Controller\IconController;
|
||||
use TYPO3\CMS\Install\Controller\LayoutController;
|
||||
use TYPO3\CMS\Install\Controller\LoginController;
|
||||
use TYPO3\CMS\Install\Controller\MaintenanceController;
|
||||
use TYPO3\CMS\Install\Controller\SettingsController;
|
||||
use TYPO3\CMS\Install\Controller\UpgradeController;
|
||||
use TYPO3\CMS\Install\Service\EnableFileService;
|
||||
use TYPO3\CMS\Install\Service\SessionService;
|
||||
|
||||
/**
|
||||
* Default request handler for all requests inside the TYPO3 Install Tool, which does a simple hardcoded
|
||||
* dispatching to a controller based on the get/post variable.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class RequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @var ConfigurationManager
|
||||
*/
|
||||
protected $configurationManager;
|
||||
|
||||
/**
|
||||
* @var array List of valid controllers
|
||||
*/
|
||||
protected $controllers = [
|
||||
'icon' => IconController::class,
|
||||
'layout' => LayoutController::class,
|
||||
'login' => LoginController::class,
|
||||
'maintenance' => MaintenanceController::class,
|
||||
'settings' => SettingsController::class,
|
||||
'upgrade' => UpgradeController::class,
|
||||
'environment' => EnvironmentController::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param ConfigurationManager $configurationManager
|
||||
*/
|
||||
public function __construct(ConfigurationManager $configurationManager)
|
||||
{
|
||||
$this->configurationManager = $configurationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an Install Tool request for normal operations
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return $this->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an Install Tool request for normal operations
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$controllerName = $request->getQueryParams()['install']['controller'] ?? 'layout';
|
||||
$actionName = $request->getParsedBody()['install']['action'] ?? $request->getQueryParams()['install']['action'] ?? 'init';
|
||||
|
||||
if ($actionName === 'showEnableInstallToolFile' && EnableFileService::isInstallToolEnableFilePermanent()) {
|
||||
$actionName = 'showLogin';
|
||||
}
|
||||
|
||||
$action = $actionName . 'Action';
|
||||
|
||||
$session = $this->initializeSession();
|
||||
if ($actionName === 'preAccessCheck') {
|
||||
$response = new JsonResponse([
|
||||
'installToolLocked' => !$this->checkEnableInstallToolFile(),
|
||||
'isAuthorized' => $session->isAuthorized()
|
||||
]);
|
||||
} elseif ($actionName === 'init') {
|
||||
$controller = new LayoutController();
|
||||
$response = $controller->initAction($request);
|
||||
} elseif ($actionName === 'checkEnableInstallToolFile') {
|
||||
$response = new JsonResponse([
|
||||
'success' => $this->checkEnableInstallToolFile(),
|
||||
]);
|
||||
} elseif ($actionName === 'showEnableInstallToolFile') {
|
||||
$controller = new LoginController();
|
||||
$response = $controller->showEnableInstallToolFileAction($request);
|
||||
} elseif ($actionName === 'checkLogin') {
|
||||
if (!$this->checkEnableInstallToolFile() && !$session->isAuthorizedBackendUserSession()) {
|
||||
throw new \RuntimeException('Not authorized', 1505563556);
|
||||
}
|
||||
if ($session->isExpired() || !$session->isAuthorized()) {
|
||||
// Session expired, log out user, start new session
|
||||
$session->resetSession();
|
||||
$session->startSession();
|
||||
$response = new JsonResponse([
|
||||
'success' => false,
|
||||
]);
|
||||
} else {
|
||||
$session->refreshSession();
|
||||
$response = new JsonResponse([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
} elseif ($actionName === 'showLogin') {
|
||||
if (!$this->checkEnableInstallToolFile()) {
|
||||
throw new \RuntimeException('Not authorized', 1505564888);
|
||||
}
|
||||
$controller = new LoginController();
|
||||
$response = $controller->showLoginAction($request);
|
||||
} elseif ($actionName === 'login') {
|
||||
if (!$this->checkEnableInstallToolFile()) {
|
||||
throw new \RuntimeException('Not authorized', 1505567462);
|
||||
}
|
||||
$this->checkSessionToken($request, $session);
|
||||
$this->checkSessionLifetime($session);
|
||||
$password = $request->getParsedBody()['install']['password'] ?? null;
|
||||
$authService = new AuthenticationService($session);
|
||||
if ($authService->loginWithPassword($password)) {
|
||||
$response = new JsonResponse([
|
||||
'success' => true,
|
||||
]);
|
||||
} else {
|
||||
if ($password === null || empty($password)) {
|
||||
$messageQueue = (new FlashMessageQueue('install'))->enqueue(
|
||||
new FlashMessage('Please enter the install tool password', '', FlashMessage::ERROR)
|
||||
);
|
||||
} else {
|
||||
$hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
|
||||
$hashedPassword = $hashInstance->getHashedPassword($password);
|
||||
$messageQueue = (new FlashMessageQueue('install'))->enqueue(
|
||||
new FlashMessage(
|
||||
'Given password does not match the install tool login password. Calculated hash: ' . $hashedPassword,
|
||||
'',
|
||||
FlashMessage::ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
$response = new JsonResponse([
|
||||
'success' => false,
|
||||
'status' => $messageQueue,
|
||||
]);
|
||||
}
|
||||
} elseif ($actionName === 'logout') {
|
||||
if (EnableFileService::installToolEnableFileExists() && !EnableFileService::isInstallToolEnableFilePermanent()) {
|
||||
EnableFileService::removeInstallToolEnableFile();
|
||||
}
|
||||
$formProtection = FormProtectionFactory::get(
|
||||
InstallToolFormProtection::class
|
||||
);
|
||||
$formProtection->clean();
|
||||
$session->destroySession();
|
||||
$response = new JsonResponse([
|
||||
'success' => true,
|
||||
]);
|
||||
} else {
|
||||
$enforceReferrerResponse = $this->enforceReferrer($request);
|
||||
if ($enforceReferrerResponse instanceof ResponseInterface) {
|
||||
return $enforceReferrerResponse;
|
||||
}
|
||||
if (
|
||||
!$this->checkSessionToken($request, $session)
|
||||
|| !$this->checkSessionLifetime($session)
|
||||
|| !$session->isAuthorized()
|
||||
) {
|
||||
return new HtmlResponse('', 403);
|
||||
}
|
||||
$session->refreshSession();
|
||||
if (!array_key_exists($controllerName, $this->controllers)) {
|
||||
throw new \RuntimeException(
|
||||
'Unknown controller ' . $controllerName,
|
||||
1505215756
|
||||
);
|
||||
}
|
||||
$this->recreatePackageStatesFileIfMissing();
|
||||
/** @var AbstractController $controller */
|
||||
$controller = new $this->controllers[$controllerName];
|
||||
if (!method_exists($controller, $action)) {
|
||||
throw new \RuntimeException(
|
||||
'Unknown action method ' . $action . ' in controller ' . $controllerName,
|
||||
1505216027
|
||||
);
|
||||
}
|
||||
$response = $controller->$action($request);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* This request handler can handle any request when not in CLI mode.
|
||||
* Warning: Order of these methods is security relevant and interferes with different access
|
||||
* conditions (new/existing installation). See the single method comments for details.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return bool Returns always TRUE
|
||||
*/
|
||||
public function canHandleRequest(ServerRequestInterface $request): bool
|
||||
{
|
||||
$basicIntegrity = $this->checkIfEssentialConfigurationExists()
|
||||
&& !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'])
|
||||
&& !EnableFileService::isFirstInstallAllowed();
|
||||
if (!$basicIntegrity) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the priority - how eager the handler is to actually handle the request.
|
||||
*
|
||||
* @return int The priority of the request handler.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if ENABLE_INSTALL_TOOL exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkEnableInstallToolFile()
|
||||
{
|
||||
return EnableFileService::checkInstallToolEnableFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize session object.
|
||||
* Subclass will throw exception if session can not be created or if
|
||||
* preconditions like a valid encryption key are not set.
|
||||
*
|
||||
* @return SessionService
|
||||
*/
|
||||
protected function initializeSession()
|
||||
{
|
||||
$session = new SessionService();
|
||||
if (!$session->hasSession()) {
|
||||
$session->startSession();
|
||||
}
|
||||
return $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use form protection API to find out if protected POST forms are ok.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param SessionService $session
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkSessionToken(ServerRequestInterface $request, SessionService $session): bool
|
||||
{
|
||||
$postValues = $request->getParsedBody()['install'];
|
||||
// no post data is there, so no token check necessary
|
||||
if (empty($postValues)) {
|
||||
return true;
|
||||
}
|
||||
$tokenOk = false;
|
||||
// A token must be given as soon as there is POST data
|
||||
if (isset($postValues['token'])) {
|
||||
$formProtection = FormProtectionFactory::get(
|
||||
InstallToolFormProtection::class
|
||||
);
|
||||
$action = (string)$postValues['action'];
|
||||
if ($action === '') {
|
||||
throw new \RuntimeException(
|
||||
'No POST action given for token check',
|
||||
1369326593
|
||||
);
|
||||
}
|
||||
$tokenOk = $formProtection->validateToken($postValues['token'], 'installTool', $action);
|
||||
}
|
||||
if (!$tokenOk) {
|
||||
$session->resetSession();
|
||||
$session->startSession();
|
||||
}
|
||||
return $tokenOk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if session expired.
|
||||
* If the session has expired, the login form is displayed.
|
||||
*
|
||||
* @param SessionService $session
|
||||
* @return bool True if session lifetime is OK
|
||||
*/
|
||||
protected function checkSessionLifetime(SessionService $session): bool
|
||||
{
|
||||
$isExpired = $session->isExpired();
|
||||
if ($isExpired) {
|
||||
// Session expired, log out user, start new session
|
||||
$session->resetSession();
|
||||
$session->startSession();
|
||||
}
|
||||
return !$isExpired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if LocalConfiguration.php and PackageStates.php exist
|
||||
*
|
||||
* @return bool TRUE when the essential configuration is available, otherwise FALSE
|
||||
*/
|
||||
protected function checkIfEssentialConfigurationExists(): bool
|
||||
{
|
||||
return file_exists($this->configurationManager->getLocalConfigurationFileLocation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create PackageStates.php if missing and LocalConfiguration exists.
|
||||
*
|
||||
* It is fired if PackageStates.php is deleted on a running instance,
|
||||
* all packages marked as "part of minimal system" are activated in this case.
|
||||
*/
|
||||
protected function recreatePackageStatesFileIfMissing(): void
|
||||
{
|
||||
if (!file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php')) {
|
||||
// We need a FailsafePackageManager at this moment, however this is given
|
||||
// As Bootstrap is registering the FailsafePackageManager object as a singleton instance
|
||||
// of the main PackageManager class. See \TYPO3\CMS\Core\Core\Bootstrap::init()
|
||||
/** @var \TYPO3\CMS\Core\Package\FailsafePackageManager $packageManager */
|
||||
$packageManager = GeneralUtility::makeInstance(PackageManager::class);
|
||||
$packages = $packageManager->getAvailablePackages();
|
||||
foreach ($packages as $package) {
|
||||
if ($package instanceof PackageInterface && $package->isPartOfMinimalUsableSystem()) {
|
||||
$packageManager->activatePackage($package->getPackageKey());
|
||||
}
|
||||
}
|
||||
$packageManager->forceSortAndSavePackageStates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates HTTP `Referer` header (which is denied by client to be a custom
|
||||
* value) - attempts to ensure the value is given using a HTML client refresh.
|
||||
* see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
protected function enforceReferrer(ServerRequestInterface $request): ?ResponseInterface
|
||||
{
|
||||
if (!(new Features())->isFeatureEnabled('security.backend.enforceReferrer')) {
|
||||
return null;
|
||||
}
|
||||
return (new ReferrerEnforcer($request))->handle([
|
||||
'flags' => ['refresh-always'],
|
||||
'subject' => 'Install Tool',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\Check;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\SetupCheck;
|
||||
use TYPO3\CMS\Reports\ExtendedStatusProviderInterface;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
use TYPO3\CMS\Reports\StatusProviderInterface;
|
||||
|
||||
/**
|
||||
* Provides an environment status report
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class EnvironmentStatusReport implements StatusProviderInterface, ExtendedStatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* Compile environment status report
|
||||
*
|
||||
* @return Status[]
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
return $this->getStatusInternal(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the detailed status of an extension or (sub)system
|
||||
*
|
||||
* @return Status[]
|
||||
*/
|
||||
public function getDetailedStatus()
|
||||
{
|
||||
return $this->getStatusInternal(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $verbose
|
||||
* @return Status[]
|
||||
*/
|
||||
protected function getStatusInternal($verbose)
|
||||
{
|
||||
$statusMessageQueue = new FlashMessageQueue('install');
|
||||
foreach (GeneralUtility::makeInstance(Check::class)->getStatus() as $message) {
|
||||
$statusMessageQueue->enqueue($message);
|
||||
}
|
||||
foreach (GeneralUtility::makeInstance(SetupCheck::class)->getStatus() as $message) {
|
||||
$statusMessageQueue->enqueue($message);
|
||||
}
|
||||
foreach (GeneralUtility::makeInstance(DatabaseCheck::class)->getStatus() as $message) {
|
||||
$statusMessageQueue->enqueue($message);
|
||||
}
|
||||
$reportStatusTypes = [
|
||||
'error' => [],
|
||||
'warning' => [],
|
||||
'ok' => [],
|
||||
'information' => [],
|
||||
'notice' => [],
|
||||
];
|
||||
foreach ($statusMessageQueue->toArray() as $message) {
|
||||
switch ($message->getSeverity()) {
|
||||
case FlashMessage::ERROR:
|
||||
$reportStatusTypes['error'][] = $message;
|
||||
break;
|
||||
case FlashMessage::WARNING:
|
||||
$reportStatusTypes['warning'][] = $message;
|
||||
break;
|
||||
case FlashMessage::OK:
|
||||
$reportStatusTypes['ok'][] = $message;
|
||||
break;
|
||||
case FlashMessage::INFO:
|
||||
$reportStatusTypes['information'][] = $message;
|
||||
break;
|
||||
case FlashMessage::NOTICE:
|
||||
$reportStatusTypes['notice'][] = $message;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$statusArray = [];
|
||||
foreach ($reportStatusTypes as $type => $statusObjects) {
|
||||
$value = count($statusObjects);
|
||||
$message = '';
|
||||
if ($verbose) {
|
||||
foreach ($statusObjects as $statusObject) {
|
||||
$message .= '### ' . $statusObject->getTitle() . ': ' . $statusObject->getSeverity() . CRLF;
|
||||
}
|
||||
}
|
||||
|
||||
if ($value > 0) {
|
||||
$pathToXliff = 'LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf';
|
||||
// Map information type to abbreviation which is used in \TYPO3\CMS\Reports\Status class
|
||||
if ($type === 'information') {
|
||||
$type = 'info';
|
||||
}
|
||||
if (!$verbose) {
|
||||
$message = $GLOBALS['LANG']->sL($pathToXliff . ':environment.status.message.' . $type);
|
||||
}
|
||||
$severity = constant('\TYPO3\CMS\Reports\Status::' . strtoupper($type));
|
||||
$statusArray[] = GeneralUtility::makeInstance(
|
||||
Status::class,
|
||||
$GLOBALS['LANG']->sL($pathToXliff . ':environment.status.title'),
|
||||
sprintf($GLOBALS['LANG']->sL($pathToXliff . ':environment.status.value'), $value),
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $statusArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Service\Exception\RemoteFetchException;
|
||||
use TYPO3\CMS\Install\Service\UpgradeWizardsService;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
|
||||
/**
|
||||
* Provides an installation status report.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class InstallStatusReport implements \TYPO3\CMS\Reports\StatusProviderInterface
|
||||
{
|
||||
protected const WRAP_FLAT = 1;
|
||||
protected const WRAP_NESTED = 2;
|
||||
|
||||
protected $useMarkup;
|
||||
|
||||
public function __construct(bool $useMarkup = true)
|
||||
{
|
||||
$this->useMarkup = $useMarkup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a collection of system status checks as a status report.
|
||||
*
|
||||
* @return Status[]
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
return [
|
||||
'FileSystem' => $this->getFileSystemStatus(),
|
||||
'RemainingUpdates' => $this->getRemainingUpdatesStatus(),
|
||||
'NewVersion' => $this->getNewVersionStatus(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for several directories being writable.
|
||||
*
|
||||
* @return Status Indicates status of the file system
|
||||
*/
|
||||
protected function getFileSystemStatus()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_writable');
|
||||
$message = '';
|
||||
$severity = Status::OK;
|
||||
// Requirement level
|
||||
// -1 = not required, but if it exists may be writable or not
|
||||
// 0 = not required, if it exists the dir should be writable
|
||||
// 1 = required, doesn't have to be writable
|
||||
// 2 = required, has to be writable
|
||||
$varPath = Environment::getVarPath();
|
||||
$sitePath = Environment::getPublicPath();
|
||||
$rootPath = Environment::getProjectPath();
|
||||
$checkWritable = [
|
||||
$sitePath . '/typo3temp/' => 2,
|
||||
$sitePath . '/typo3temp/assets/' => 2,
|
||||
$sitePath . '/typo3temp/assets/compressed/' => 2,
|
||||
// only needed when GraphicalFunctions is used
|
||||
$sitePath . '/typo3temp/assets/images/' => 0,
|
||||
// used in PageGenerator (inlineStyle2Temp) and Backend + Language JS files
|
||||
$sitePath . '/typo3temp/assets/css/' => 2,
|
||||
$sitePath . '/typo3temp/assets/js/' => 2,
|
||||
// fallback storage of FAL
|
||||
$sitePath . '/typo3temp/assets/_processed_/' => 0,
|
||||
$varPath => 2,
|
||||
$varPath . '/transient/' => 2,
|
||||
$varPath . '/charset/' => 2,
|
||||
$varPath . '/lock/' => 2,
|
||||
$sitePath . '/typo3conf/' => 2,
|
||||
Environment::getLabelsPath() => 0,
|
||||
$sitePath . '/' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] => -1,
|
||||
$sitePath . '/' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . '_temp_/' => 0,
|
||||
];
|
||||
|
||||
// Check for writable extension folder files in non-composer mode only
|
||||
if (!Environment::isComposerMode()) {
|
||||
$checkWritable[Environment::getExtensionsPath()] = 0;
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['EXT']['allowGlobalInstall']) {
|
||||
$checkWritable[Environment::getBackendPath() . '/ext/'] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($checkWritable as $path => $requirementLevel) {
|
||||
$relPath = substr($path, strlen($rootPath) + 1);
|
||||
if (!@is_dir($path)) {
|
||||
// If the directory is missing, try to create it
|
||||
GeneralUtility::mkdir($path);
|
||||
}
|
||||
if (!@is_dir($path)) {
|
||||
if ($requirementLevel > 0) {
|
||||
// directory is required
|
||||
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_missingDirectory');
|
||||
$message .= sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryDoesNotExistCouldNotCreate'), $relPath) . '<br />';
|
||||
$severity = Status::ERROR;
|
||||
} else {
|
||||
$message .= sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryDoesNotExist'), $relPath);
|
||||
if ($requirementLevel == 0) {
|
||||
$message .= ' ' . $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryShouldAlsoBeWritable');
|
||||
}
|
||||
$message .= '<br />';
|
||||
if ($severity < Status::WARNING) {
|
||||
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_nonExistingDirectory');
|
||||
$severity = Status::WARNING;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!is_writable($path)) {
|
||||
switch ($requirementLevel) {
|
||||
case 0:
|
||||
$message .= sprintf(
|
||||
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryShouldBeWritable'),
|
||||
$path
|
||||
) . '<br />';
|
||||
if ($severity < Status::WARNING) {
|
||||
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_recommendedWritableDirectory');
|
||||
$severity = Status::WARNING;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_requiredWritableDirectory');
|
||||
$message .= sprintf(
|
||||
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryMustBeWritable'),
|
||||
$path
|
||||
) . '<br />';
|
||||
$severity = Status::ERROR;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return GeneralUtility::makeInstance(Status::class, $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_fileSystem'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all incomplete update wizards.
|
||||
*
|
||||
* Fetches all wizards that are not marked "done" in the registry and filters out
|
||||
* the ones that should not be rendered (= no upgrade required).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getIncompleteWizards(): array
|
||||
{
|
||||
$upgradeWizardsService = GeneralUtility::makeInstance(UpgradeWizardsService::class);
|
||||
$incompleteWizards = $upgradeWizardsService->getUpgradeWizardsList();
|
||||
$incompleteWizards = array_filter(
|
||||
$incompleteWizards,
|
||||
function ($wizard) {
|
||||
return $wizard['shouldRenderWizard'];
|
||||
}
|
||||
);
|
||||
return $incompleteWizards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are still updates to perform
|
||||
*
|
||||
* @return Status Represents whether the installation is completely updated yet
|
||||
*/
|
||||
protected function getRemainingUpdatesStatus()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$value = $languageService->getLL('status_updateComplete');
|
||||
$message = '';
|
||||
$severity = Status::OK;
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
// check if there are update wizards left to perform
|
||||
$incompleteWizards = $this->getIncompleteWizards();
|
||||
if (count($incompleteWizards)) {
|
||||
// At least one incomplete wizard was found
|
||||
$value = $languageService->getLL('status_updateIncomplete');
|
||||
$severity = Status::WARNING;
|
||||
$url = (string)$uriBuilder->buildUriFromRoute('tools_toolsupgrade');
|
||||
$message = sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_update'), '<a href="' . htmlspecialchars($url) . '">', '</a>');
|
||||
}
|
||||
|
||||
return GeneralUtility::makeInstance(Status::class, $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_remainingUpdates'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there is a new minor TYPO3 version to update to.
|
||||
*
|
||||
* @return Status Represents whether there is a new version available online
|
||||
*/
|
||||
protected function getNewVersionStatus()
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
/** @var \TYPO3\CMS\Install\Service\CoreVersionService $coreVersionService */
|
||||
$coreVersionService = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\CoreVersionService::class);
|
||||
|
||||
// No updates for development versions
|
||||
if (!$coreVersionService->isInstalledVersionAReleasedVersion()) {
|
||||
return GeneralUtility::makeInstance(Status::class, 'TYPO3', TYPO3_version, $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_isDevelopmentVersion'), Status::NOTICE);
|
||||
}
|
||||
|
||||
try {
|
||||
$versionMaintenanceWindow = $coreVersionService->getMaintenanceWindow();
|
||||
} catch (RemoteFetchException $remoteFetchException) {
|
||||
return GeneralUtility::makeInstance(
|
||||
Status::class,
|
||||
'TYPO3',
|
||||
TYPO3_version,
|
||||
$languageService->sL(
|
||||
'LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_remoteFetchException'
|
||||
),
|
||||
Status::NOTICE
|
||||
);
|
||||
}
|
||||
|
||||
if (!$versionMaintenanceWindow->isSupportedByCommunity() && !$versionMaintenanceWindow->isSupportedByElts()) {
|
||||
// Version is not maintained
|
||||
$message = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_versionOutdated');
|
||||
$status = Status::ERROR;
|
||||
} else {
|
||||
$message = '';
|
||||
$status = Status::OK;
|
||||
|
||||
// There is an update available
|
||||
$availableReleases = [];
|
||||
$latestRelease = $coreVersionService->getYoungestPatchRelease();
|
||||
$isCurrentVersionElts = $coreVersionService->isCurrentInstalledVersionElts();
|
||||
|
||||
if ($coreVersionService->isPatchReleaseSuitableForUpdate($latestRelease)) {
|
||||
$availableReleases[] = $latestRelease;
|
||||
}
|
||||
|
||||
if (!$versionMaintenanceWindow->isSupportedByCommunity()) {
|
||||
if ($latestRelease->isElts()) {
|
||||
$latestCommunityDrivenRelease = $coreVersionService->getYoungestCommunityPatchRelease();
|
||||
if ($coreVersionService->isPatchReleaseSuitableForUpdate($latestCommunityDrivenRelease)) {
|
||||
$availableReleases[] = $latestCommunityDrivenRelease;
|
||||
}
|
||||
} elseif (!$isCurrentVersionElts) {
|
||||
// Inform user about ELTS being available soon if:
|
||||
// - regular support ran out
|
||||
// - the current installed version is no ELTS
|
||||
// - no ELTS update was released, yet
|
||||
$message = sprintf(
|
||||
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_elts_information'),
|
||||
TYPO3_version,
|
||||
'<a href="https://typo3.com/elts" target="_blank" rel="noopener">https://typo3.com/elts</a>'
|
||||
);
|
||||
$status = Status::WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
if ($availableReleases !== []) {
|
||||
$messages = [];
|
||||
$status = Status::WARNING;
|
||||
foreach ($availableReleases as $availableRelease) {
|
||||
$versionString = $availableRelease->getVersion();
|
||||
if ($availableRelease->isElts()) {
|
||||
$versionString .= ' ELTS';
|
||||
}
|
||||
if ($coreVersionService->isUpdateSecurityRelevant($availableRelease)) {
|
||||
$status = Status::ERROR;
|
||||
$updateMessage = sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_newVersionSecurityRelevant'), $versionString);
|
||||
} else {
|
||||
$updateMessage = sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_newVersion'), $versionString);
|
||||
}
|
||||
|
||||
if ($availableRelease->isElts()) {
|
||||
if ($isCurrentVersionElts) {
|
||||
$updateMessage .= ' ' . sprintf(
|
||||
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_elts_download'),
|
||||
'<a href="https://my.typo3.org" target="_blank" rel="noopener">my.typo3.org</a>'
|
||||
);
|
||||
} else {
|
||||
$updateMessage .= ' ' . sprintf(
|
||||
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_elts_subscribe'),
|
||||
$coreVersionService->getInstalledVersion(),
|
||||
'<a href="https://typo3.com/elts" target="_blank" rel="noopener">https://typo3.com/elts</a>'
|
||||
);
|
||||
}
|
||||
}
|
||||
$messages[] = $updateMessage;
|
||||
}
|
||||
$message = $this->wrapList($messages, count($messages) > 1 ? self::WRAP_NESTED : self::WRAP_FLAT);
|
||||
}
|
||||
}
|
||||
|
||||
return GeneralUtility::makeInstance(Status::class, 'TYPO3', TYPO3_version, $message, $status);
|
||||
}
|
||||
|
||||
protected function wrapList(array $items, int $style): string
|
||||
{
|
||||
if (!$this->useMarkup) {
|
||||
return implode(', ', $items);
|
||||
}
|
||||
if ($style === self::WRAP_NESTED) {
|
||||
return sprintf(
|
||||
'<ul>%s</ul>',
|
||||
implode('', $this->wrapItems($items, '<li>', '</li>'))
|
||||
);
|
||||
}
|
||||
return sprintf(
|
||||
'<p>%s</p>',
|
||||
implode('', $this->wrapItems($items, '<br>', ''))
|
||||
);
|
||||
}
|
||||
|
||||
protected function wrapItems(array $items, string $before, string $after): array
|
||||
{
|
||||
return array_map(
|
||||
static function (string $item) use ($before, $after): string {
|
||||
return $before . $item . $after;
|
||||
},
|
||||
array_filter($items)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\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!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Service\EnableFileService;
|
||||
use TYPO3\CMS\Install\SystemEnvironment\ServerResponse\ServerResponseCheck;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
|
||||
/**
|
||||
* Provides an status report of the security of the install tool
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class SecurityStatusReport implements \TYPO3\CMS\Reports\StatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* Compiles a collection of system status checks as a status report.
|
||||
*
|
||||
* @return Status[]
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
$this->executeAdminCommand();
|
||||
return [
|
||||
'installToolPassword' => $this->getInstallToolPasswordStatus(),
|
||||
'installToolProtection' => $this->getInstallToolProtectionStatus(),
|
||||
'serverResponseStatus' => GeneralUtility::makeInstance(ServerResponseCheck::class)->asStatus(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Install Tool password is set to its default value.
|
||||
*
|
||||
* @return Status An object representing the security of the install tool password
|
||||
*/
|
||||
protected function getInstallToolPasswordStatus()
|
||||
{
|
||||
// @todo @deprecated: This should be removed in TYPO3 v10.0 when install tool allows proper hashes only
|
||||
$value = $GLOBALS['LANG']->getLL('status_ok');
|
||||
$message = '';
|
||||
$severity = Status::OK;
|
||||
$isDefaultPassword = false;
|
||||
$installToolPassword = $GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'];
|
||||
$hashInstance = null;
|
||||
$hashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
|
||||
try {
|
||||
$hashInstance = $hashFactory->get($installToolPassword, 'BE');
|
||||
} catch (InvalidPasswordHashException $e) {
|
||||
// $hashInstance stays null
|
||||
$value = $GLOBALS['LANG']->getLL('status_wrongValue');
|
||||
$message = $e->getMessage();
|
||||
$severity = Status::ERROR;
|
||||
}
|
||||
if ($installToolPassword !== '' && $hashInstance !== null) {
|
||||
$isDefaultPassword = $hashInstance->checkPassword('joh316', $installToolPassword);
|
||||
} elseif ($installToolPassword === 'bacb98acf97e0b6112b1d1b650b84971') {
|
||||
// using MD5 of legacy default password 'joh316'
|
||||
$isDefaultPassword = true;
|
||||
}
|
||||
if ($isDefaultPassword) {
|
||||
$value = $GLOBALS['LANG']->getLL('status_insecure');
|
||||
$severity = Status::ERROR;
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
$changeInstallToolPasswordUrl = (string)$uriBuilder->buildUriFromRoute('tools_toolssettings');
|
||||
$message = sprintf(
|
||||
$GLOBALS['LANG']->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.installtool_default_password'),
|
||||
'<a href="' . htmlspecialchars($changeInstallToolPasswordUrl) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
return GeneralUtility::makeInstance(
|
||||
Status::class,
|
||||
$GLOBALS['LANG']->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_installToolPassword'),
|
||||
$value,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the existence of the ENABLE_INSTALL_TOOL file.
|
||||
*
|
||||
* @return Status An object representing whether ENABLE_INSTALL_TOOL exists
|
||||
*/
|
||||
protected function getInstallToolProtectionStatus()
|
||||
{
|
||||
$enableInstallToolFile = Environment::getPublicPath() . '/' . EnableFileService::INSTALL_TOOL_ENABLE_FILE_PATH;
|
||||
$value = $GLOBALS['LANG']->getLL('status_disabled');
|
||||
$message = '';
|
||||
$severity = Status::OK;
|
||||
if (EnableFileService::installToolEnableFileExists()) {
|
||||
if (EnableFileService::isInstallToolEnableFilePermanent()) {
|
||||
$severity = Status::WARNING;
|
||||
$disableInstallToolUrl = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL') . '&adminCmd=remove_ENABLE_INSTALL_TOOL';
|
||||
$value = $GLOBALS['LANG']->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_enabledPermanently');
|
||||
$message = sprintf(
|
||||
$GLOBALS['LANG']->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_enabled'),
|
||||
'<code style="white-space: nowrap;">' . $enableInstallToolFile . '</code>'
|
||||
);
|
||||
$message .= ' <a href="' . htmlspecialchars($disableInstallToolUrl) . '">' .
|
||||
$GLOBALS['LANG']->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_enabled_cmd') . '</a>';
|
||||
} else {
|
||||
if (EnableFileService::installToolEnableFileLifetimeExpired()) {
|
||||
EnableFileService::removeInstallToolEnableFile();
|
||||
} else {
|
||||
$severity = Status::NOTICE;
|
||||
$disableInstallToolUrl = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL') . '&adminCmd=remove_ENABLE_INSTALL_TOOL';
|
||||
$value = $GLOBALS['LANG']->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_enabledTemporarily');
|
||||
$message = sprintf(
|
||||
$GLOBALS['LANG']->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_installEnabledTemporarily'),
|
||||
'<code style="white-space: nowrap;">' . $enableInstallToolFile . '</code>',
|
||||
floor((@filemtime($enableInstallToolFile) + EnableFileService::INSTALL_TOOL_ENABLE_FILE_LIFETIME - time()) / 60)
|
||||
);
|
||||
$message .= ' <a href="' . htmlspecialchars($disableInstallToolUrl) . '">' .
|
||||
$GLOBALS['LANG']->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_enabled_cmd') . '</a>';
|
||||
}
|
||||
}
|
||||
}
|
||||
return GeneralUtility::makeInstance(
|
||||
Status::class,
|
||||
$GLOBALS['LANG']->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_installTool'),
|
||||
$value,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes commands like removing the Install Tool enable file.
|
||||
*/
|
||||
protected function executeAdminCommand()
|
||||
{
|
||||
$command = GeneralUtility::_GET('adminCmd');
|
||||
switch ($command) {
|
||||
case 'remove_ENABLE_INSTALL_TOOL':
|
||||
EnableFileService::removeInstallToolEnableFile();
|
||||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\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\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Basic service to clear caches within the install tool.
|
||||
* @internal This is NOT an API class, it is for internal use in the install tool only.
|
||||
*/
|
||||
class ClearCacheService
|
||||
{
|
||||
/**
|
||||
* This clear cache implementation follows a pretty brutal approach.
|
||||
* Goal is to reliably get rid of cache entries, even if some broken
|
||||
* extension is loaded that would kill the backend 'clear cache' action.
|
||||
*
|
||||
* Therefor this method "knows" implementation details of the cache
|
||||
* framework and uses them to clear all file based cache (typo3temp/Cache)
|
||||
* and database caches (tables prefixed with cf_) manually.
|
||||
*
|
||||
* After that ext_tables and ext_localconf of extensions are loaded, those
|
||||
* may register additional caches in the caching framework with different
|
||||
* backend, and will then clear them with the usual flush() method.
|
||||
*/
|
||||
public function clearAll()
|
||||
{
|
||||
// Delete typo3temp/Cache
|
||||
GeneralUtility::flushDirectory(Environment::getVarPath() . '/cache', true, true);
|
||||
|
||||
// Get all table names from Default connection starting with 'cf_' and truncate them
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$connection = $connectionPool->getConnectionByName('Default');
|
||||
$tableNames = $connection->getSchemaManager()->listTableNames();
|
||||
foreach ($tableNames as $tableName) {
|
||||
if (strpos($tableName, 'cf_') === 0 || $tableName === 'cache_treelist') {
|
||||
$connection->truncate($tableName);
|
||||
}
|
||||
}
|
||||
|
||||
// check tables on other connections
|
||||
$remappedTables = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'])
|
||||
? array_keys((array)$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'])
|
||||
: [];
|
||||
foreach ($remappedTables as $tableName) {
|
||||
if (strpos((string)$tableName, 'cf_') === 0 || $tableName === 'cache_treelist') {
|
||||
$connectionPool->getConnectionForTable($tableName)->truncate($tableName);
|
||||
}
|
||||
}
|
||||
|
||||
// From this point on, the code may fatal, if some broken extension is loaded.
|
||||
|
||||
// Use bootstrap to load all ext_localconf and ext_tables
|
||||
Bootstrap::loadTypo3LoadedExtAndExtLocalconf(false);
|
||||
Bootstrap::unsetReservedGlobalVariables();
|
||||
Bootstrap::loadBaseTca(false);
|
||||
Bootstrap::loadExtTables(false);
|
||||
|
||||
// The cache manager is already instantiated in the install tool
|
||||
// with some hacked settings to disable caching of extbase and fluid.
|
||||
// We want a "fresh" object here to operate on a different cache setup.
|
||||
// cacheManager implements SingletonInterface, so the only way to get a "fresh"
|
||||
// instance is by circumventing makeInstance and/or the objectManager and
|
||||
// using new directly!
|
||||
$cacheManager = new \TYPO3\CMS\Core\Cache\CacheManager();
|
||||
$cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
|
||||
$cacheManager->flushCaches();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\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\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Service handling clearing and statistics of semi-persistent
|
||||
* core tables.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ClearTableService
|
||||
{
|
||||
/**
|
||||
* @var array List of table and their description
|
||||
*/
|
||||
protected $tableList = [
|
||||
[
|
||||
'name' => 'be_sessions',
|
||||
'description' => 'Backend user sessions'
|
||||
],
|
||||
[
|
||||
'name' => 'fe_sessions',
|
||||
'description' => 'Frontend user sessions',
|
||||
],
|
||||
[
|
||||
'name' => 'sys_history',
|
||||
'description' => 'Tracking of database record changes through TYPO3 backend forms',
|
||||
],
|
||||
[
|
||||
'name' => 'sys_lockedrecords',
|
||||
'description' => 'Record locking of backend user editing',
|
||||
],
|
||||
[
|
||||
'name' => 'sys_log',
|
||||
'description' => 'General log table',
|
||||
],
|
||||
[
|
||||
'name' => 'sys_preview',
|
||||
'description' => 'Workspace preview links',
|
||||
],
|
||||
[
|
||||
'name' => 'tx_extensionmanager_domain_model_extension',
|
||||
'description' => 'List of TER extensions',
|
||||
],
|
||||
[
|
||||
'name' => 'tx_rsaauth_keys',
|
||||
'description' => 'Login process key storage'
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Get an array of all affected tables, a short description and their row counts
|
||||
*
|
||||
* @return array Details per table
|
||||
*/
|
||||
public function getTableStatistics(): array
|
||||
{
|
||||
$tableStatistics = [];
|
||||
foreach ($this->tableList as $table) {
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table['name']);
|
||||
if ($connection->getSchemaManager()->tablesExist([$table['name']])) {
|
||||
$table['rowCount'] = $connection->count(
|
||||
'*',
|
||||
$table['name'],
|
||||
[]
|
||||
);
|
||||
$tableStatistics[] = $table;
|
||||
}
|
||||
}
|
||||
return $tableStatistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate a table from $this->tableList
|
||||
*
|
||||
* @param string $tableName
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function clearSelectedTable(string $tableName)
|
||||
{
|
||||
$tableFound = false;
|
||||
foreach ($this->tableList as $table) {
|
||||
if ($table['name'] === $tableName) {
|
||||
$tableFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$tableFound) {
|
||||
throw new \RuntimeException(
|
||||
'Selected table ' . $tableName . ' can not be cleared',
|
||||
1501942151
|
||||
);
|
||||
}
|
||||
GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName)->truncate($tableName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,562 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\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\Core\Environment;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Service\OpcodeCacheService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
use TYPO3\CMS\Install\CoreVersion\CoreRelease;
|
||||
use TYPO3\CMS\Install\FolderStructure\DefaultFactory;
|
||||
use TYPO3\CMS\Install\Service\Exception\RemoteFetchException;
|
||||
|
||||
/**
|
||||
* Core update service.
|
||||
* This service handles core updates, all the nasty details are encapsulated
|
||||
* here. The single public methods 'depend' on each other, for example a new
|
||||
* core has to be downloaded before it can be unpacked.
|
||||
*
|
||||
* Each method returns only TRUE of FALSE indicating if it was successful or
|
||||
* not. Detailed information can be fetched with getMessages() and will return
|
||||
* a list of status messages of the previous operation.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class CoreUpdateService
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Service\CoreVersionService
|
||||
*/
|
||||
protected $coreVersionService;
|
||||
|
||||
/**
|
||||
* @var FlashMessageQueue
|
||||
*/
|
||||
protected $messages;
|
||||
|
||||
/**
|
||||
* Absolute path to download location
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $downloadTargetPath;
|
||||
|
||||
/**
|
||||
* Absolute path to the symlink pointing to the currently used TYPO3 core files
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $symlinkToCoreFiles;
|
||||
|
||||
/**
|
||||
* Base URI for TYPO3 downloads
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $downloadBaseUri;
|
||||
|
||||
/**
|
||||
* @param CoreVersionService $coreVersionService
|
||||
*/
|
||||
public function __construct(CoreVersionService $coreVersionService = null)
|
||||
{
|
||||
$this->coreVersionService = $coreVersionService ?: GeneralUtility::makeInstance(CoreVersionService::class);
|
||||
$this->setDownloadTargetPath(Environment::getVarPath() . '/transient/');
|
||||
$this->symlinkToCoreFiles = $this->discoverCurrentCoreSymlink();
|
||||
$this->downloadBaseUri = 'https://get.typo3.org';
|
||||
$this->messages = new FlashMessageQueue('install');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this installation wants to enable the core updater
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCoreUpdateEnabled()
|
||||
{
|
||||
$coreUpdateDisabled = getenv('TYPO3_DISABLE_CORE_UPDATER') ?: (getenv('REDIRECT_TYPO3_DISABLE_CORE_UPDATER') ?: false);
|
||||
return !Environment::isComposerMode() && !$coreUpdateDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* In future implementations we might implement some smarter logic here
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function discoverCurrentCoreSymlink()
|
||||
{
|
||||
return Environment::getPublicPath() . '/typo3_src';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create download location in case the folder does not exist
|
||||
* @todo move this to folder structure
|
||||
*
|
||||
* @param string $downloadTargetPath
|
||||
*/
|
||||
protected function setDownloadTargetPath($downloadTargetPath)
|
||||
{
|
||||
if (!is_dir($downloadTargetPath)) {
|
||||
GeneralUtility::mkdir_deep($downloadTargetPath);
|
||||
}
|
||||
$this->downloadTargetPath = $downloadTargetPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get messages of previous method call
|
||||
*
|
||||
* @return FlashMessageQueue
|
||||
*/
|
||||
public function getMessages(): FlashMessageQueue
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper method for CoreVersionService
|
||||
*
|
||||
* @deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0 - use REST api directly (see https://get.typo3.org/v1/api/doc)
|
||||
* @return bool TRUE on success
|
||||
*/
|
||||
public function updateVersionMatrix()
|
||||
{
|
||||
trigger_error(
|
||||
'The method updateVersionMatrix() is deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0, use the REST api directly (see https://get.typo3.org/v1/api/doc).',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$success = true;
|
||||
try {
|
||||
$this->coreVersionService->getYoungestPatchRelease();
|
||||
} catch (RemoteFetchException $e) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'Current version specification could not be fetched from https://get.typo3.org.'
|
||||
. ' This is probably a network issue, please fix it.',
|
||||
'Version information could not be fetched from get.typo3.org',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an update is possible at all
|
||||
*
|
||||
* @param CoreRelease $coreRelease The target core release
|
||||
* @return bool TRUE on success
|
||||
*/
|
||||
public function checkPreConditions(CoreRelease $coreRelease)
|
||||
{
|
||||
$success = true;
|
||||
|
||||
// Folder structure test: Update can be done only if folder structure returns no errors
|
||||
$folderStructureFacade = GeneralUtility::makeInstance(DefaultFactory::class)->getStructure();
|
||||
$folderStructureMessageQueue = $folderStructureFacade->getStatus();
|
||||
$folderStructureErrors = $folderStructureMessageQueue->getAllMessages(FlashMessage::ERROR);
|
||||
$folderStructureWarnings = $folderStructureMessageQueue->getAllMessages(FlashMessage::WARNING);
|
||||
if (!empty($folderStructureErrors) || !empty($folderStructureWarnings) || !is_link(Environment::getPublicPath() . '/typo3_src')) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'To perform an update, the folder structure of this TYPO3 CMS instance must'
|
||||
. ' stick to the conventions, or the update process could lead to unexpected'
|
||||
. ' results and may be hazardous to your system',
|
||||
'Automatic TYPO3 CMS core update not possible: Folder structure has errors or warnings',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
}
|
||||
|
||||
// No core update on windows
|
||||
if (Environment::isWindows()) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Automatic TYPO3 CMS core update not possible: Update not supported on Windows OS',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
// Explicit write check to document root
|
||||
$file = Environment::getPublicPath() . '/' . StringUtility::getUniqueId('install-core-update-test-');
|
||||
$result = @touch($file);
|
||||
if (!$result) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'Could not write a file in path "' . Environment::getPublicPath() . '/"!',
|
||||
'Automatic TYPO3 CMS core update not possible: No write access to document root',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
// Check symlink creation
|
||||
$link = Environment::getPublicPath() . '/' . StringUtility::getUniqueId('install-core-update-test-');
|
||||
@symlink($file, $link);
|
||||
if (!is_link($link)) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'Could not create a symbolic link in path "' . Environment::getPublicPath() . '/"!',
|
||||
'Automatic TYPO3 CMS core update not possible: No symlink creation possible',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
unlink($link);
|
||||
}
|
||||
unlink($file);
|
||||
}
|
||||
|
||||
if (!$this->checkCoreFilesAvailable($coreRelease->getVersion())) {
|
||||
// Explicit write check to upper directory of current core location
|
||||
$coreLocation = @realpath($this->symlinkToCoreFiles . '/../');
|
||||
$file = $coreLocation . '/' . StringUtility::getUniqueId('install-core-update-test-');
|
||||
$result = @touch($file);
|
||||
if (!$result) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'New TYPO3 CMS core should be installed in "' . $coreLocation . '", but this directory is not writable!',
|
||||
'Automatic TYPO3 CMS core update not possible: No write access to TYPO3 CMS core location',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($success && !$this->coreVersionService->isInstalledVersionAReleasedVersion()) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'Your current version is specified as ' . $this->coreVersionService->getInstalledVersion() . '.'
|
||||
. ' This is a development version and can not be updated automatically. If this is a "git"'
|
||||
. ' checkout, please update using git directly.',
|
||||
'Automatic TYPO3 CMS core update not possible: You are running a development version of TYPO3',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the specified version
|
||||
*
|
||||
* @param CoreRelease $coreRelease A core release to download
|
||||
* @return bool TRUE on success
|
||||
*/
|
||||
public function downloadVersion(CoreRelease $coreRelease)
|
||||
{
|
||||
$version = $coreRelease->getVersion();
|
||||
$success = true;
|
||||
if ($this->checkCoreFilesAvailable($version)) {
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Skipped download of TYPO3 CMS core. A core source directory already exists in destination path. Using this instead.',
|
||||
FlashMessage::NOTICE
|
||||
));
|
||||
} else {
|
||||
$downloadUri = $this->downloadBaseUri . '/' . $version;
|
||||
$fileLocation = $this->getDownloadTarGzTargetPath($version);
|
||||
|
||||
if (@file_exists($fileLocation)) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'TYPO3 CMS core download exists in download location: ' . PathUtility::stripPathSitePrefix($this->downloadTargetPath),
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$fileContent = GeneralUtility::getUrl($downloadUri);
|
||||
if (!$fileContent) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'Failed to download ' . $downloadUri,
|
||||
'Download not successful',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$fileStoreResult = file_put_contents($fileLocation, $fileContent);
|
||||
if (!$fileStoreResult) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Unable to store download content',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'TYPO3 CMS core download finished'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify checksum of downloaded version
|
||||
*
|
||||
* @param CoreRelease $coreRelease A downloaded core release to check
|
||||
* @return bool TRUE on success
|
||||
*/
|
||||
public function verifyFileChecksum(CoreRelease $coreRelease)
|
||||
{
|
||||
$version = $coreRelease->getVersion();
|
||||
$success = true;
|
||||
if ($this->checkCoreFilesAvailable($version)) {
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Verifying existing TYPO3 CMS core checksum is not possible',
|
||||
FlashMessage::WARNING
|
||||
));
|
||||
} else {
|
||||
$fileLocation = $this->getDownloadTarGzTargetPath($version);
|
||||
$expectedChecksum = $coreRelease->getChecksum();
|
||||
if (!file_exists($fileLocation)) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Downloaded TYPO3 CMS core not found',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$actualChecksum = sha1_file($fileLocation);
|
||||
if ($actualChecksum !== $expectedChecksum) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'The official TYPO3 CMS version system on https://get.typo3.org expects a sha1 checksum of '
|
||||
. $expectedChecksum . ' from the content of the downloaded new TYPO3 CMS core version ' . $version . '.'
|
||||
. ' The actual checksum is ' . $actualChecksum . '. The update is stopped. This may be a'
|
||||
. ' failed download, an attack, or an issue with the typo3.org infrastructure.',
|
||||
'New TYPO3 CMS core checksum mismatch',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Checksum verified'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack a downloaded core
|
||||
*
|
||||
* @param CoreRelease $coreRelease A core release to unpack
|
||||
* @return bool TRUE on success
|
||||
*/
|
||||
public function unpackVersion(CoreRelease $coreRelease)
|
||||
{
|
||||
$version = $coreRelease->getVersion();
|
||||
$success = true;
|
||||
if ($this->checkCoreFilesAvailable($version)) {
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Unpacking TYPO3 CMS core files skipped',
|
||||
FlashMessage::NOTICE
|
||||
));
|
||||
} else {
|
||||
$fileLocation = $this->downloadTargetPath . $version . '.tar.gz';
|
||||
if (!@is_file($fileLocation)) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Downloaded TYPO3 CMS core not found',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} elseif (@file_exists($this->downloadTargetPath . 'typo3_src-' . $version)) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Unpacked TYPO3 CMS core exists in download location: ' . PathUtility::stripPathSitePrefix($this->downloadTargetPath),
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$unpackCommand = 'tar xf ' . escapeshellarg($fileLocation) . ' -C ' . escapeshellarg($this->downloadTargetPath) . ' 2>&1';
|
||||
exec($unpackCommand, $output, $errorCode);
|
||||
if ($errorCode) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Unpacking TYPO3 CMS core not successful',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$removePackedFileResult = unlink($fileLocation);
|
||||
if (!$removePackedFileResult) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Removing packed TYPO3 CMS core not successful',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Unpacking TYPO3 CMS core successful'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an unpacked core to its final destination
|
||||
*
|
||||
* @param CoreRelease $coreRelease A core release to move
|
||||
* @return bool TRUE on success
|
||||
*/
|
||||
public function moveVersion(CoreRelease $coreRelease)
|
||||
{
|
||||
$version = $coreRelease->getVersion();
|
||||
$success = true;
|
||||
if ($this->checkCoreFilesAvailable($version)) {
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Moving TYPO3 CMS core files skipped',
|
||||
FlashMessage::NOTICE
|
||||
));
|
||||
} else {
|
||||
$downloadedCoreLocation = $this->downloadTargetPath . 'typo3_src-' . $version;
|
||||
$newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
|
||||
|
||||
if (!@is_dir($downloadedCoreLocation)) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Unpacked TYPO3 CMS core not found',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$moveResult = rename($downloadedCoreLocation, $newCoreLocation);
|
||||
if (!$moveResult) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Moving TYPO3 CMS core to ' . $newCoreLocation . ' failed',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Moved TYPO3 CMS core to final location'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a core version
|
||||
*
|
||||
* @param CoreRelease $coreRelease A core release to activate
|
||||
* @return bool TRUE on success
|
||||
*/
|
||||
public function activateVersion(CoreRelease $coreRelease)
|
||||
{
|
||||
$newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $coreRelease->getVersion();
|
||||
$success = true;
|
||||
if (!is_dir($newCoreLocation)) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'New TYPO3 CMS core not found',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} elseif (!is_link($this->symlinkToCoreFiles)) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'TYPO3 CMS core source directory (typo3_src) is not a link',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
$isCurrentCoreSymlinkAbsolute = PathUtility::isAbsolutePath(readlink($this->symlinkToCoreFiles));
|
||||
$unlinkResult = unlink($this->symlinkToCoreFiles);
|
||||
if (!$unlinkResult) {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Removing old symlink failed',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
} else {
|
||||
if (!$isCurrentCoreSymlinkAbsolute) {
|
||||
$newCoreLocation = $this->getRelativePath($newCoreLocation);
|
||||
}
|
||||
$symlinkResult = symlink($newCoreLocation, $this->symlinkToCoreFiles);
|
||||
if ($symlinkResult) {
|
||||
GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
|
||||
} else {
|
||||
$success = false;
|
||||
$this->messages->enqueue(new FlashMessage(
|
||||
'',
|
||||
'Linking new TYPO3 CMS core failed',
|
||||
FlashMessage::ERROR
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute path of downloaded .tar.gz
|
||||
*
|
||||
* @param string $version A version number
|
||||
* @return string
|
||||
*/
|
||||
protected function getDownloadTarGzTargetPath($version)
|
||||
{
|
||||
return $this->downloadTargetPath . $version . '.tar.gz';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relative path to TYPO3 source directory from webroot
|
||||
*
|
||||
* @param string $absolutePath to TYPO3 source directory
|
||||
* @return string relative path to TYPO3 source directory
|
||||
*/
|
||||
protected function getRelativePath($absolutePath)
|
||||
{
|
||||
$sourcePath = explode(DIRECTORY_SEPARATOR, Environment::getPublicPath());
|
||||
$targetPath = explode(DIRECTORY_SEPARATOR, rtrim($absolutePath, DIRECTORY_SEPARATOR));
|
||||
while (count($sourcePath) && count($targetPath) && $sourcePath[0] === $targetPath[0]) {
|
||||
array_shift($sourcePath);
|
||||
array_shift($targetPath);
|
||||
}
|
||||
return str_pad('', count($sourcePath) * 3, '..' . DIRECTORY_SEPARATOR) . implode(DIRECTORY_SEPARATOR, $targetPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is are already core files available
|
||||
* at the download destination.
|
||||
*
|
||||
* @param string $version A version number
|
||||
* @return bool true when core files are available
|
||||
*/
|
||||
protected function checkCoreFilesAvailable($version)
|
||||
{
|
||||
$newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
|
||||
return @is_dir($newCoreLocation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\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\Registry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
|
||||
use TYPO3\CMS\Install\CoreVersion\CoreRelease;
|
||||
use TYPO3\CMS\Install\CoreVersion\MaintenanceWindow;
|
||||
use TYPO3\CMS\Install\CoreVersion\MajorRelease;
|
||||
|
||||
/**
|
||||
* Core version service
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class CoreVersionService
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Registry
|
||||
*/
|
||||
protected $registry;
|
||||
|
||||
/**
|
||||
* Base URI for TYPO3 Version REST api
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $apiBaseUrl = 'https://get.typo3.org/v1/api/';
|
||||
|
||||
/**
|
||||
* Initialize update URI
|
||||
*
|
||||
* @param Registry $registry Deprecated
|
||||
*/
|
||||
public function __construct(Registry $registry = null)
|
||||
{
|
||||
if (null !== $registry) {
|
||||
trigger_error(
|
||||
'The constructor parameter $registry for CoreVersionService is deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$this->registry = $registry;
|
||||
} else {
|
||||
$this->registry = GeneralUtility::makeInstance(Registry::class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Development git checkout versions always end with '-dev'. They are
|
||||
* not "released" as such and can not be updated.
|
||||
*
|
||||
* @return bool FALSE If some development version is installed
|
||||
*/
|
||||
public function isInstalledVersionAReleasedVersion(): bool
|
||||
{
|
||||
$version = $this->getInstalledVersion();
|
||||
return substr($version, -4) !== '-dev';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current installed version number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInstalledVersion(): string
|
||||
{
|
||||
return VersionNumberUtility::getCurrentTypo3Version();
|
||||
}
|
||||
|
||||
public function getMaintenanceWindow(): MaintenanceWindow
|
||||
{
|
||||
$url = 'major/' . $this->getInstalledMajorVersion();
|
||||
$result = $this->fetchFromRemote($url);
|
||||
|
||||
return MaintenanceWindow::fromApiResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{community: string[], elts: string[]}
|
||||
*/
|
||||
public function getSupportedMajorReleases(): array
|
||||
{
|
||||
$url = 'major';
|
||||
$result = $this->fetchFromRemote($url);
|
||||
|
||||
$majorReleases = [
|
||||
'community' => [],
|
||||
'elts' => [],
|
||||
];
|
||||
foreach ($result as $release) {
|
||||
$majorRelease = MajorRelease::fromApiResponse($release);
|
||||
$maintenanceWindow = $majorRelease->getMaintenanceWindow();
|
||||
|
||||
if ($maintenanceWindow->isSupportedByCommunity()) {
|
||||
$group = 'community';
|
||||
} elseif ($maintenanceWindow->isSupportedByElts()) {
|
||||
$group = 'elts';
|
||||
} else {
|
||||
// Major version is unsupported
|
||||
continue;
|
||||
}
|
||||
|
||||
$majorReleases[$group][] = $majorRelease->getLts() ?? $majorRelease->getVersion();
|
||||
}
|
||||
|
||||
return $majorReleases;
|
||||
}
|
||||
|
||||
public function isPatchReleaseSuitableForUpdate(CoreRelease $coreRelease): bool
|
||||
{
|
||||
return version_compare($this->getInstalledVersion(), $coreRelease->getVersion()) === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if a younger patch level release exists in version matrix.
|
||||
*
|
||||
* @return bool TRUE if younger patch release is exists
|
||||
* @throws \TYPO3\CMS\Install\Service\Exception\RemoteFetchException
|
||||
*/
|
||||
public function isYoungerPatchReleaseAvailable(): bool
|
||||
{
|
||||
return version_compare($this->getInstalledVersion(), $this->getYoungestPatchRelease()->getVersion()) === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if an upgrade from current version is security relevant
|
||||
*
|
||||
* @return bool TRUE if there is a pending security update
|
||||
* @throws \TYPO3\CMS\Install\Service\Exception\RemoteFetchException
|
||||
*/
|
||||
public function isUpdateSecurityRelevant(CoreRelease $releaseToCheck): bool
|
||||
{
|
||||
$url = 'major/' . $this->getInstalledMajorVersion() . '/release';
|
||||
$result = $this->fetchFromRemote($url);
|
||||
|
||||
$installedVersion = $this->getInstalledVersion();
|
||||
foreach ($result as $release) {
|
||||
$coreRelease = CoreRelease::fromApiResponse($release);
|
||||
if ($coreRelease->isSecurityUpdate()
|
||||
&& version_compare($installedVersion, $coreRelease->getVersion()) === -1 // installed version is lower than release
|
||||
&& version_compare($releaseToCheck->getVersion(), $coreRelease->getVersion()) > -1 // release to check is equal or higher than release
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isCurrentInstalledVersionElts(): bool
|
||||
{
|
||||
$url = 'major/' . $this->getInstalledMajorVersion() . '/release';
|
||||
$result = $this->fetchFromRemote($url);
|
||||
|
||||
$installedVersion = $this->getInstalledVersion();
|
||||
foreach ($result as $release) {
|
||||
if (version_compare($installedVersion, $release['version']) === 0) {
|
||||
return $release['elts'] ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Youngest patch release
|
||||
*
|
||||
* @return CoreRelease
|
||||
* @throws \TYPO3\CMS\Install\Service\Exception\RemoteFetchException
|
||||
*/
|
||||
public function getYoungestPatchRelease(): CoreRelease
|
||||
{
|
||||
$url = 'major/' . $this->getInstalledMajorVersion() . '/release/latest';
|
||||
$result = $this->fetchFromRemote($url);
|
||||
return CoreRelease::fromApiResponse($result);
|
||||
}
|
||||
|
||||
public function getYoungestCommunityPatchRelease(): CoreRelease
|
||||
{
|
||||
$url = 'major/' . $this->getInstalledMajorVersion() . '/release';
|
||||
$result = $this->fetchFromRemote($url);
|
||||
|
||||
// Make sure all releases are sorted by their version
|
||||
$columns = array_column($result, 'version');
|
||||
array_multisort($columns, SORT_NATURAL, $result);
|
||||
|
||||
// Remove any ELTS release
|
||||
$releases = array_filter($result, static function (array $release) {
|
||||
return ($release['elts'] ?? false) === false;
|
||||
});
|
||||
|
||||
$latestRelease = end($releases);
|
||||
|
||||
return CoreRelease::fromApiResponse($latestRelease);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return array
|
||||
* @throws \TYPO3\CMS\Install\Service\Exception\RemoteFetchException
|
||||
*/
|
||||
protected function fetchFromRemote(string $url): array
|
||||
{
|
||||
$url = $this->apiBaseUrl . $url;
|
||||
$json = GeneralUtility::getUrl($url);
|
||||
|
||||
if (!$json) {
|
||||
$this->throwFetchException($url);
|
||||
}
|
||||
return json_decode($json, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 'major version' from installed version of TYPO3, e.g., '7' from '7.3.0'
|
||||
*
|
||||
* @return string For example 7
|
||||
*/
|
||||
protected function getInstalledMajorVersion(): string
|
||||
{
|
||||
return $this->getMajorVersion($this->getInstalledVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 'major version' of version, e.g., '7' from '7.3.0'
|
||||
*
|
||||
* @param string $version to check
|
||||
* @return string Major version, e.g., '7'
|
||||
*/
|
||||
protected function getMajorVersion(string $version): string
|
||||
{
|
||||
$explodedVersion = explode('.', $version);
|
||||
return $explodedVersion[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update version matrix from remote and store in registry
|
||||
*
|
||||
* @deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0 - use new REST API directly (see https://get.typo3.org/v1/api/doc)
|
||||
* @throws Exception\RemoteFetchException
|
||||
*/
|
||||
public function updateVersionMatrix(): void
|
||||
{
|
||||
trigger_error('Method updateVersionMatrix will be removed in TYPO3 v10.0, use new REST API directly (see https://get.typo3.org/v1/api/doc).', E_USER_DEPRECATED);
|
||||
$url = 'https://get.typo3.org/json';
|
||||
$versionJson = GeneralUtility::getUrl($url);
|
||||
if (!$versionJson) {
|
||||
$this->throwFetchException($url);
|
||||
}
|
||||
$versionArray = json_decode($versionJson, true);
|
||||
$installedMajorVersion = (int)$this->getInstalledMajorVersion();
|
||||
|
||||
foreach ($versionArray as $versionNumber => $versionDetails) {
|
||||
if (is_array($versionDetails) && (int)$this->getMajorVersion((string)$versionNumber) < $installedMajorVersion) {
|
||||
unset($versionArray[$versionNumber]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->registry->set('TYPO3.CMS.Install', 'coreVersionMatrix', $versionArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Youngest development patch release, e.g., 6.2.0alpha3 or 6.2-snapshot-20131004
|
||||
*
|
||||
* @deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0 - TYPO3 release cycles do not contain development releases anymore
|
||||
* @return string
|
||||
* @throws \TYPO3\CMS\Install\Service\Exception\RemoteFetchException
|
||||
*/
|
||||
public function getYoungestPatchDevelopmentRelease(): string
|
||||
{
|
||||
trigger_error(
|
||||
'Method getYoungestPatchDevelopmentRelease() is deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0, use getYoungestPatchRelease() instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
return $this->getYoungestPatchRelease()->getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if a younger patch level release exists in version matrix that may be a development release.
|
||||
*
|
||||
* @deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0 - TYPO3 release cycles do not contain development releases anymore
|
||||
* @return bool TRUE if younger patch release is exists
|
||||
* @throws \TYPO3\CMS\Install\Service\Exception\RemoteFetchException
|
||||
*/
|
||||
public function isYoungerPatchDevelopmentReleaseAvailable(): bool
|
||||
{
|
||||
trigger_error(
|
||||
'Method isYoungerPatchDevelopmentReleaseAvailable() is deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0, use isYoungerPatchReleaseAvailable() instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
return $this->isYoungerPatchReleaseAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0, use 'https://get.typo3.org' directly
|
||||
* @return string
|
||||
*/
|
||||
public function getDownloadBaseUrl(): string
|
||||
{
|
||||
trigger_error(
|
||||
'Method getDownloadBaseUrl() is deprecated since TYPO3 v9 and will be removed in TYPO3 v10.0, use https://get.typo3.org directly.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
return $this->apiBaseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to throw same exception in multiple places
|
||||
*
|
||||
* @param string $url
|
||||
* @throws \TYPO3\CMS\Install\Service\Exception\RemoteFetchException
|
||||
*/
|
||||
protected function throwFetchException(string $url): void
|
||||
{
|
||||
throw new Exception\RemoteFetchException(
|
||||
'Fetching ' .
|
||||
$url .
|
||||
' failed. Maybe this instance can not connect to the remote system properly.',
|
||||
1380897593
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\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\Core\Environment;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Basic Service to check and create install tool files
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class EnableFileService
|
||||
{
|
||||
/**
|
||||
* @var string Relative path to ENABLE_INSTALL_TOOL file
|
||||
*/
|
||||
const INSTALL_TOOL_ENABLE_FILE_PATH = 'typo3conf/ENABLE_INSTALL_TOOL';
|
||||
|
||||
/**
|
||||
* @var string Relative path to FIRST_INSTALL file
|
||||
*/
|
||||
const FIRST_INSTALL_FILE_PATH = 'FIRST_INSTALL';
|
||||
|
||||
/**
|
||||
* @var string Maximum age of ENABLE_INSTALL_TOOL file before it gets removed (in seconds)
|
||||
*/
|
||||
const INSTALL_TOOL_ENABLE_FILE_LIFETIME = 3600;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isFirstInstallAllowed()
|
||||
{
|
||||
$files = self::getFirstInstallFilePaths();
|
||||
if (!empty($files)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the INSTALL_TOOL_ENABLE file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function createInstallToolEnableFile()
|
||||
{
|
||||
$installEnableFilePath = self::getInstallToolEnableFilePath();
|
||||
if (!is_file($installEnableFilePath)) {
|
||||
$result = touch($installEnableFilePath);
|
||||
} else {
|
||||
$result = true;
|
||||
self::extendInstallToolEnableFileLifetime();
|
||||
}
|
||||
GeneralUtility::fixPermissions($installEnableFilePath);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the INSTALL_TOOL_ENABLE file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function removeInstallToolEnableFile()
|
||||
{
|
||||
return unlink(self::getInstallToolEnableFilePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the FIRST_INSTALL file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function removeFirstInstallFile()
|
||||
{
|
||||
$result = true;
|
||||
$files = self::getFirstInstallFilePaths();
|
||||
foreach ($files as $file) {
|
||||
$result = unlink(Environment::getPublicPath() . '/' . $file) && $result;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the install tool file exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function installToolEnableFileExists()
|
||||
{
|
||||
return @is_file(self::getInstallToolEnableFilePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the install tool file exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkInstallToolEnableFile()
|
||||
{
|
||||
if (!self::installToolEnableFileExists()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::isInstallToolEnableFilePermanent()) {
|
||||
if (self::installToolEnableFileLifetimeExpired()) {
|
||||
self::removeInstallToolEnableFile();
|
||||
return false;
|
||||
}
|
||||
self::extendInstallToolEnableFileLifetime();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the install tool file should be kept
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInstallToolEnableFilePermanent()
|
||||
{
|
||||
if (self::installToolEnableFileExists()) {
|
||||
$content = @file_get_contents(self::getInstallToolEnableFilePath());
|
||||
if (strpos($content, 'KEEP_FILE') !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the lifetime of the install tool file is expired
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function installToolEnableFileLifetimeExpired()
|
||||
{
|
||||
if (time() - @filemtime(self::getInstallToolEnableFilePath()) > self::INSTALL_TOOL_ENABLE_FILE_LIFETIME) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last modification of the ENABLE_INSTALL_TOOL file
|
||||
*/
|
||||
protected static function extendInstallToolEnableFileLifetime()
|
||||
{
|
||||
$enableFile = self::getInstallToolEnableFilePath();
|
||||
// Extend the age of the ENABLE_INSTALL_TOOL file by one hour
|
||||
if (is_file($enableFile)) {
|
||||
$couldTouch = @touch($enableFile);
|
||||
if (!$couldTouch) {
|
||||
// If we can't remove the creation method will call us again.
|
||||
if (self::removeInstallToolEnableFile()) {
|
||||
self::createInstallToolEnableFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the INSTALL_TOOL_ENABLE file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getInstallToolEnableFilePath()
|
||||
{
|
||||
return Environment::getPublicPath() . '/' . self::INSTALL_TOOL_ENABLE_FILE_PATH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths to the FIRST_INSTALL files
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function getFirstInstallFilePaths()
|
||||
{
|
||||
$files = array_filter(scandir(Environment::getPublicPath() . '/'), function ($file) {
|
||||
return @is_file(Environment::getPublicPath() . '/' . $file) && preg_match('~^' . self::FIRST_INSTALL_FILE_PATH . '.*~i', $file);
|
||||
});
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\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!
|
||||
*/
|
||||
|
||||
/**
|
||||
* A service exception
|
||||
*/
|
||||
class Exception extends \TYPO3\CMS\Install\Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Service\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\Install\Service\Exception;
|
||||
|
||||
/**
|
||||
* An exception thrown if the silent configuration updater changed configuration
|
||||
*/
|
||||
class ConfigurationChangedException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Service\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\Install\Service\Exception;
|
||||
|
||||
/**
|
||||
* An exception thrown if version validation against official version matrix fails
|
||||
*/
|
||||
class CoreVersionServiceException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Service\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 thrown if fetching a resource from a remote server fails
|
||||
*/
|
||||
class RemoteFetchException extends CoreVersionServiceException
|
||||
{
|
||||
}
|
||||
Loaded 100 of 12716 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user