*
* This class handles the access to the files
* - EXT:core/Configuration/DefaultConfiguration.php (default TYPO3_CONF_VARS)
- * - typo3conf/LocalConfiguration.php (overrides of TYPO3_CONF_VARS)
- * - typo3conf/AdditionalConfiguration.php (optional additional local code blocks)
+ * - LocalConfiguration.php (overrides of TYPO3_CONF_VARS)
+ * - AdditionalConfiguration.php (optional additional local code blocks)
*
* IMPORTANT:
* This class is intended for internal core use ONLY.
protected $defaultConfigurationDescriptionFile = 'typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml';
/**
- * @var string Path to local overload TYPO3_CONF_VARS file, relative to the public web folder
+ * @var string Path to local overload TYPO3_CONF_VARS file, relative to Environment::getConfigPath()
*/
- protected $localConfigurationFile = 'typo3conf/LocalConfiguration.php';
+ protected $localConfigurationFile = 'LocalConfiguration.php';
/**
- * @var string Path to additional local file, relative to the public web folder
+ * @var string Path to additional local file, relative to Environment::getConfigPath()
*/
- protected $additionalConfigurationFile = 'typo3conf/AdditionalConfiguration.php';
+ protected $additionalConfigurationFile = 'AdditionalConfiguration.php';
/**
* @var string Path to factory configuration file used during installation as LocalConfiguration boilerplate
/**
* @var string Path to possible additional factory configuration file delivered by packages
*/
- protected $additionalFactoryConfigurationFile = 'typo3conf/AdditionalFactoryConfiguration.php';
+ protected $additionalFactoryConfigurationFile = 'AdditionalFactoryConfiguration.php';
/**
* Writing to these configuration paths is always allowed,
}
/**
- * Return local configuration array typo3conf/LocalConfiguration.php
+ * Return local configuration array Environment::getConfigPath() / LocalConfiguration.php
*
* @return array Content array of local configuration file
*/
*/
public function getLocalConfigurationFileLocation()
{
- return Environment::getPublicPath() . '/' . $this->localConfigurationFile;
+ return Environment::getConfigPath() . '/' . $this->localConfigurationFile;
}
/**
*/
public function getAdditionalConfigurationFileLocation()
{
- return Environment::getPublicPath() . '/' . $this->additionalConfigurationFile;
+ return Environment::getConfigPath() . '/' . $this->additionalConfigurationFile;
}
/**
*/
protected function getAdditionalFactoryConfigurationFileLocation()
{
- return Environment::getPublicPath() . '/' . $this->additionalFactoryConfigurationFile;
+ return Environment::getConfigPath() . '/' . $this->additionalFactoryConfigurationFile;
}
/**
public function canWriteConfiguration()
{
$fileLocation = $this->getLocalConfigurationFileLocation();
- return @is_writable(file_exists($fileLocation) ? $fileLocation : Environment::getPublicPath() . '/typo3conf/');
+ return @is_writable(file_exists($fileLocation) ? $fileLocation : Environment::getConfigPath());
}
/**
}
/**
- * Write local configuration array to typo3conf/LocalConfiguration.php
+ * Write local configuration array to Environment::getConfigPath() . /LocalConfiguration.php
*
* @param array $configuration The local configuration to be written
* @throws \RuntimeException
}
/**
- * Write additional configuration array to typo3conf/AdditionalConfiguration.php
+ * Write additional configuration array to Environment::getConfigPath() . /AdditionalConfiguration.php
*
* @param array $additionalConfigurationLines The configuration lines to be written
* @throws \RuntimeException
/**
* Uses FactoryConfiguration file and a possible AdditionalFactoryConfiguration
- * file in typo3conf to create a basic LocalConfiguration.php. This is used
+ * file in Environment::getConfigPath() to create a basic LocalConfiguration.php. This is used
* by the install tool in an early step.
*
* @throws \RuntimeException
*
* This class does not use any TYPO3 instance specific configuration, it only
* sets up things based on the server environment and core code. Even with a
- * missing typo3conf/localconf.php this script will be successful.
+ * missing LocalConfiguration.php this script will be successful.
*
* The script aborts execution with an error message if
* some part fails or conditions are not met.
public function __construct(DependencyOrderingService $dependencyOrderingService = null)
{
$this->packagesBasePath = Environment::getPublicPath() . '/';
- $this->packageStatesPathAndFilename = Environment::getPublicPath() . '/typo3conf/PackageStates.php';
+ $this->packageStatesPathAndFilename = Environment::getLegacyConfigPath() . '/PackageStates.php';
if ($dependencyOrderingService === null) {
trigger_error(self::class . ' without constructor based dependency injection has been deprecated in v9.2 and will not work in TYPO3 v10.', E_USER_DEPRECATED);
$dependencyOrderingService = GeneralUtility::makeInstance(DependencyOrderingService::class);
<?php
/**
- * This is a boilerplate of typo3conf/LocalConfiguration.php. It is
+ * This is a boilerplate of LocalConfiguration.php. It is
* used as base file during installation and can be overloaded with
- * a package specific file typo3conf/AdditionalFactoryConfiguration.php
+ * a package specific file AdditionalFactoryConfiguration.php
* from eg. the government or introduction package.
*/
return [
--- /dev/null
+.. include:: ../../Includes.txt
+
+====================================================================================
+Important: #84623 - Configuration files moved from "typo3conf" to "config" directory
+====================================================================================
+
+See :issue:`84623`
+
+Description
+===========
+
+The TYPO3 configuration files for instances running in Composer Mode or having the environment
+variable `TYPO3_PATH_APP` set to a different location than :php:`PATH_site`, have been moved from
+the :file:`<web-dir>/typo3conf` to the :file:`config` directory.
+
+The following files are affected:
+
+- :file:`LocalConfiguration.php`
+- :file:`AdditionalConfiguration.php`
+- :file:`AdditionalFactoryConfiguration.php`
+
+The first access to the TYPO3 frontend or backend will redirect to the Install Tool which
+automatically creates the :file:`config` directory and moves all of the affected files currently
+present in :file:`<web-dir>/typo3conf` to :file:`config`. Afterwards backend and frontend are
+accessible as usual.
+
+Using the environment variable `TYPO3_PATH_APP` allows for storing relevant code like configuration
+outside of the document root / Composer `<web-dir>`. This effectively hardens a TYPO3 instance,
+as less files are accessible in public. All new installations in Composer Mode use this setup by
+default. Installations in Classic Mode (without Composer setup) will continue to use the previous
+setup since it cannot be ensured that they have access outside of the document root.
+
+If you have a setup with a $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] set, please
+go to the install tool first. The migration is done automatically and you can just go to the backend
+afterwards. If you have not set your trustedHostsPattern, you will be redirected automatically and
+you don't have to do anything.
+
+.. index:: LocalConfiguration
$this->markTestSkipped('Test skipped if run on linux as root');
}
/** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
- $subject = $this->getAccessibleMock(ConfigurationManager::class, ['dummy']);
+ $subject = $this->getAccessibleMock(ConfigurationManager::class, ['getLocalConfigurationFileLocation']);
$file = 'typo3temp/var/tests/' . $this->getUniqueId('test_');
$absoluteFile = Environment::getPublicPath() . '/' . $file;
chmod($absoluteFile, 0444);
clearstatcache();
- $subject->_set('localConfigurationFile', $file);
+ $subject
+ ->method('getLocalConfigurationFileLocation')
+ ->will($this->returnValue($absoluteFile));
$result = $subject->canWriteConfiguration();
public function canWriteConfigurationReturnsTrueIfDirectoryAndFilesAreWritable(): void
{
/** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
- $subject = $this->getAccessibleMock(ConfigurationManager::class, ['dummy']);
+ $subject = $this->getAccessibleMock(ConfigurationManager::class, ['getLocalConfigurationFileLocation']);
$directory = 'typo3temp/var/tests/' . $this->getUniqueId('test_');
$absoluteDirectory = Environment::getPublicPath() . '/' . $directory;
$absoluteFile1 = Environment::getPublicPath() . '/' . $file;
touch($absoluteFile1);
$this->testFilesToDelete[] = $absoluteFile1;
- $subject->_set('localConfigurationFile', $absoluteFile1);
-
clearstatcache();
+ $subject
+ ->expects($this->any())
+ ->method('getLocalConfigurationFileLocation')
+ ->will($this->returnValue($absoluteFile1));
$result = $subject->canWriteConfiguration();
$this->assertTrue($result);
$this->expectException(\RuntimeException::class);
/** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
- $subject = $this->getAccessibleMock(ConfigurationManager::class, ['dummy']);
+ $subject = $this->getAccessibleMock(ConfigurationManager::class, ['getLocalConfigurationFileLocation']);
$file = 'typo3temp/var/tests/' . $this->getUniqueId('test_');
$absoluteFile = Environment::getPublicPath() . '/' . $file;
touch($absoluteFile);
$this->testFilesToDelete[] = $absoluteFile;
- $subject->_set('localConfigurationFile', $file);
+ $subject
+ ->expects($this->any())
+ ->method('getLocalConfigurationFileLocation')
+ ->will($this->returnValue($absoluteFile));
$subject->createLocalConfigurationFromFactoryConfiguration();
}
public function createLocalConfigurationFromFactoryConfigurationWritesContentFromFactoryFile(): void
{
/** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
- $subject = $this->getAccessibleMock(ConfigurationManager::class, ['writeLocalConfiguration']);
- $subject->_set('localConfigurationFile', 'typo3temp/var/tests/' . $this->getUniqueId('dummy_'));
+ $subject = $this->getAccessibleMock(ConfigurationManager::class, ['writeLocalConfiguration', 'getLocalConfigurationFileLocation']);
+ $subject
+ ->expects($this->any())
+ ->method('getLocalConfigurationFileLocation')
+ ->will($this->returnValue(PATH_site . 'typo3temp/var/tests/' . $this->getUniqueId('dummy_')));
$factoryConfigurationFile = 'typo3temp/var/tests/' . $this->getUniqueId('test_') . '.php';
$factoryConfigurationAbsoluteFile = Environment::getPublicPath() . '/' . $factoryConfigurationFile;
public function createLocalConfigurationFromFactoryConfigurationMergesConfigurationWithAdditionalFactoryFile(): void
{
/** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
- $subject = $this->getAccessibleMock(ConfigurationManager::class, ['writeLocalConfiguration']);
- $subject->_set('localConfigurationFile', 'typo3temp/var/tests/' . $this->getUniqueId('dummy_'));
+ $subject = $this->getAccessibleMock(ConfigurationManager::class, ['writeLocalConfiguration', 'getAdditionalFactoryConfigurationFileLocation', 'getLocalConfigurationFileLocation']);
+ $subject
+ ->expects($this->any())
+ ->method('getLocalConfigurationFileLocation')
+ ->will($this->returnValue(PATH_site . 'typo3temp/var/tests/' . $this->getUniqueId('dummy_')));
$factoryConfigurationFile = 'typo3temp/var/tests/' . $this->getUniqueId('test_') . '.php';
$factoryConfigurationAbsoluteFile = Environment::getPublicPath() . '/' . $factoryConfigurationFile;
);
$this->testFilesToDelete[] = $additionalFactoryConfigurationAbsoluteFile;
$subject->_set('additionalFactoryConfigurationFile', $additionalFactoryConfigurationFile);
+ $subject
+ ->expects($this->any())
+ ->method('getAdditionalFactoryConfigurationFileLocation')
+ ->will($this->returnValue($additionalFactoryConfigurationAbsoluteFile));
$subject
->expects($this->once())
*/
public function getDirsReturnsArrayOfDirectoriesFromGivenDirectory()
{
- $directories = GeneralUtility::get_dirs(Environment::getLegacyConfigPath() . '/');
+ $directories = GeneralUtility::get_dirs(Environment::getBackendPath() . '/');
$this->assertInternalType(\PHPUnit\Framework\Constraint\IsType::TYPE_ARRAY, $directories);
}
public function checkEnvironmentAndFoldersAction(): ResponseInterface
{
return new JsonResponse([
- 'success' => @is_file(Environment::getLegacyConfigPath() . '/LocalConfiguration.php'),
+ 'success' => @is_file(Environment::getConfigPath() . '/LocalConfiguration.php'),
]);
}
$structureFixMessageQueue = $structureFacade->fix();
$errorsFromStructure = $structureFixMessageQueue->getAllMessages(FlashMessage::ERROR);
- if (@is_dir(Environment::getLegacyConfigPath())) {
+ if (@is_dir(Environment::getConfigPath())) {
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
$configurationManager->createLocalConfigurationFromFactoryConfiguration();
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
use TYPO3\CMS\Core\Http\JsonResponse;
$view->assignMultiple([
'siteName' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
'loginToken' => $formProtection->generateToken('installTool', 'login'),
+ 'configPath' => str_replace(Environment::getProjectPath(), '<project-path>', Environment::getConfigPath()),
]);
return new JsonResponse([
'success' => true,
use TYPO3\CMS\Core\Http\AbstractApplication;
use TYPO3\CMS\Core\Http\RequestHandlerInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Install\Service\SilentConfigurationUpgradeService;
/**
* Entry point for the TYPO3 Install Tool
*/
protected function handle(ServerRequestInterface $request): ResponseInterface
{
+ // Migrate LocalConfiguration.php to a new file location, if TYPO3_PATH_APP is used.
+ // The functionality will be removed in TYPO3 v11.
+ GeneralUtility::makeInstance(SilentConfigurationUpgradeService::class)->migrateConfigurationFiles();
+
$this->initializeContext();
+
foreach ($this->availableRequestHandlers as $handler) {
if ($handler->canHandleRequest($request)) {
return $handler->handleRequest($request);
*/
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\BcryptPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface;
}
/**
+ * Migration wizard to move configuration files from "<web-dir>/typo3conf/" to "config/"
+ * This applies to the files where all installation-wide configuration resides,
+ * and is called each time when the installer is hit.
+ *
+ * This only applies if the environment variable "TYPO3_PATH_APP" is used and the new configuration
+ * files are not in place yet.
+ *
+ * This functionality will be removed in TYPO3 v11, as all installations should be migrated by then.
+ * @internal
+ */
+ public function migrateConfigurationFiles()
+ {
+ if (Environment::getConfigPath() !== rtrim(PATH_typo3conf, '/')) {
+ if (!file_exists(Environment::getConfigPath())) {
+ GeneralUtility::mkdir(Environment::getConfigPath());
+ }
+
+ $filesToCheck = [
+ 'LocalConfiguration.php',
+ 'AdditionalConfiguration.php',
+ 'AdditionalFactoryConfiguration.php',
+ ];
+ $reloadConfiguration = false;
+ foreach ($filesToCheck as $filename) {
+ $oldFilepath = PATH_typo3conf . $filename;
+ $newFilepath = Environment::getConfigPath() . '/' . $filename;
+
+ if (file_exists($oldFilepath) && !file_exists($newFilepath)) {
+ $reloadConfiguration = true;
+ rename($oldFilepath, $newFilepath);
+ }
+ }
+ if ($reloadConfiguration) {
+ $this->configurationManager->exportConfiguration();
+ }
+ }
+ }
+
+ /**
* Executed configuration upgrades. Single upgrade methods must throw a
* ConfigurationChangedException if something was written to LocalConfiguration.
*
<div class="panel-heading"><h3 class="panel-title">Important</h3></div>
<div class="panel-body">
If you don't know the current password, you can set a new one by setting the value of
- <code>$GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword']</code> in <code>typo3conf/LocalConfiguration.php</code> to
+ <code>$GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword']</code> in <code>{configPath}/LocalConfiguration.php</code> to
the hash value of the password you desire, which will be shown if you enter the desired password
in this form and submit it.
<br /><br />
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Core\ApplicationContext;
+use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\BcryptPasswordHash;
use TYPO3\CMS\Core\Package\PackageManager;
class SilentConfigurationUpgradeServiceTest extends UnitTestCase
{
/**
- * @var ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject
+ * @var ConfigurationManager|\PHPUnit\Framework\MockObject\MockObject
*/
protected $configurationManager;
protected $backupPackageManager;
/**
+ * restore Environment after each test
+ * @var bool
+ */
+ protected $backupEnvironment = true;
+
+ /**
* Set up
*/
protected function setUp()
{
$this->backupPackageManager = ExtensionManagementUtilityAccessibleProxy::getPackageManager();
+ parent::setUp();
}
/**
}
/**
+ * Moves existing files in PATH_site . typo3conf/ to a different location
+ * @test
+ */
+ public function configurationFilesAreMovedToConfigDirectoryIfEnvironmentVariableIsSet()
+ {
+ Environment::initialize(
+ new ApplicationContext('Development'),
+ true,
+ false,
+ rtrim(PATH_site, '/'),
+ rtrim(PATH_site, '/'),
+ PATH_site . 'var',
+ PATH_site . 'typo3temp/var/tests/' . uniqid('config', true),
+ PATH_thisScript,
+ 'WINDOWS'
+ );
+
+ $filesToCreate = [
+ 'LocalConfiguration.php',
+ 'AdditionalConfiguration.php',
+ 'AdditionalFactoryConfiguration.php',
+ ];
+
+ foreach ($filesToCreate as $fileToCreate) {
+ touch(PATH_typo3conf . $fileToCreate);
+ }
+
+ $this->createConfigurationManagerWithMockedMethods(['exportConfiguration']);
+ $this->configurationManager->expects($this->once())->method('exportConfiguration');
+
+ $subject = new SilentConfigurationUpgradeService($this->configurationManager);
+ $subject->migrateConfigurationFiles();
+
+ foreach ($filesToCreate as $file) {
+ $this->testFilesToDelete[] = Environment::getConfigPath() . '/' . $file;
+
+ // Check if the new file is placed
+ $this->assertFileExists(Environment::getConfigPath() . '/' . $file);
+ // Check if the old files do not exist anymore
+ $this->assertFileNotExists(PATH_typo3conf . $file);
+ }
+ }
+
+ /**
* Dataprovider for configureBackendLoginSecurity
*
* @return array