From 3e43c115397e44240698cf98ab8a8ed2bd1648b6 Mon Sep 17 00:00:00 2001 From: Sven Wappler Date: Mon, 10 Aug 2026 22:31:31 +0200 Subject: [PATCH] TYPO3 v15 dev-main snapshot () --- .gitignore | 1 + .../Authentication/AuthenticationService.php | 173 + Classes/Command/BackendUserGroupType.php | 63 + Classes/Command/PasswordSetCommand.php | 164 + Classes/Command/SetupCommand.php | 774 ++ .../SetupDefaultBackendUserGroupsCommand.php | 125 + .../Configuration/AbstractCustomPreset.php | 110 + Classes/Configuration/AbstractFeature.php | 131 + Classes/Configuration/AbstractPreset.php | 152 + Classes/Configuration/Cache/CacheFeature.php | 40 + .../Configuration/Cache/CustomCachePreset.php | 36 + .../Cache/DatabaseCachePreset.php | 53 + .../Configuration/Cache/FileCachePreset.php | 53 + .../Configuration/Context/ContextFeature.php | 40 + .../Configuration/Context/CustomPreset.php | 36 + Classes/Configuration/Context/DebugPreset.php | 77 + Classes/Configuration/Context/LivePreset.php | 77 + .../Configuration/CustomPresetInterface.php | 37 + Classes/Configuration/Exception.php | 21 + Classes/Configuration/FeatureInterface.php | 43 + Classes/Configuration/FeatureManager.php | 126 + .../Image/AbstractImagePreset.php | 152 + Classes/Configuration/Image/CustomPreset.php | 41 + .../Image/GraphicsMagickPreset.php | 93 + Classes/Configuration/Image/ImageFeature.php | 55 + .../Image/ImageMagick6Preset.php | 102 + Classes/Configuration/Mail/CustomPreset.php | 42 + Classes/Configuration/Mail/MailFeature.php | 40 + Classes/Configuration/Mail/SendmailPreset.php | 93 + Classes/Configuration/Mail/SmtpPreset.php | 77 + .../PasswordHashing/Argon2iPreset.php | 57 + .../PasswordHashing/Argon2idPreset.php | 57 + .../PasswordHashing/BcryptPreset.php | 57 + .../PasswordHashing/CustomPreset.php | 67 + .../PasswordHashingFeature.php | 45 + .../PasswordHashing/Pbkdf2Preset.php | 57 + .../PasswordHashing/PhpassPreset.php | 57 + Classes/Configuration/PresetInterface.php | 82 + Classes/Controller/AbstractController.php | 60 + .../Controller/BackendModuleController.php | 128 + Classes/Controller/ControllerTrait.php | 46 + .../EntryPointRedirectController.php | 34 + Classes/Controller/EnvironmentController.php | 1450 ++++ Classes/Controller/IconController.php | 58 + Classes/Controller/InstallerController.php | 640 ++ Classes/Controller/LayoutController.php | 170 + Classes/Controller/LoginController.php | 68 + Classes/Controller/MaintenanceController.php | 891 +++ .../ServerResponseCheckController.php | 56 + Classes/Controller/SettingsController.php | 654 ++ Classes/Controller/UpgradeController.php | 1300 +++ Classes/CoreVersion/CoreRelease.php | 68 + Classes/CoreVersion/MaintenanceWindow.php | 53 + Classes/CoreVersion/MajorRelease.php | 56 + Classes/Database/PermissionsCheck.php | 221 + Classes/Exception.php | 23 + .../ExtensionScanner/CodeScannerInterface.php | 29 + .../ExtensionScanner/Php/CodeStatistics.php | 121 + .../Php/GeneratorClassesResolver.php | 135 + .../Php/Matcher/AbstractCoreMatcher.php | 236 + .../AbstractMethodImplementationMatcher.php | 148 + .../Php/Matcher/ArrayDimensionMatcher.php | 95 + .../Php/Matcher/ArrayGlobalMatcher.php | 65 + .../Php/Matcher/ClassConstantMatcher.php | 64 + .../Php/Matcher/ClassNameMatcher.php | 63 + .../Php/Matcher/ConstantMatcher.php | 62 + .../Matcher/ConstructorArgumentMatcher.php | 240 + .../Php/Matcher/FunctionCallMatcher.php | 75 + .../Matcher/InterfaceMethodChangedMatcher.php | 105 + .../Php/Matcher/MethodAnnotationMatcher.php | 87 + .../Matcher/MethodArgumentDroppedMatcher.php | 85 + .../MethodArgumentDroppedStaticMatcher.php | 105 + .../Matcher/MethodArgumentRequiredMatcher.php | 85 + .../MethodArgumentRequiredStaticMatcher.php | 110 + .../Matcher/MethodArgumentUnusedMatcher.php | 92 + .../MethodCallArgumentValueMatcher.php | 133 + .../Php/Matcher/MethodCallMatcher.php | 85 + .../Php/Matcher/MethodCallStaticMatcher.php | 108 + .../Php/Matcher/PropertyAnnotationMatcher.php | 90 + .../Matcher/PropertyExistsStaticMatcher.php | 64 + .../Php/Matcher/PropertyProtectedMatcher.php | 69 + .../Php/Matcher/PropertyPublicMatcher.php | 68 + .../Php/Matcher/ScalarStringMatcher.php | 75 + .../ExtensionScanner/Php/MatcherFactory.php | 103 + Classes/Factory/ImportMapFactory.php | 84 + Classes/FolderStructure/AbstractNode.php | 230 + Classes/FolderStructure/DefaultFactory.php | 406 + .../DefaultPermissionsCheck.php | 122 + Classes/FolderStructure/DirectoryNode.php | 310 + Classes/FolderStructure/Exception.php | 21 + .../Exception/InvalidArgumentException.php | 23 + .../Exception/RootNodeException.php | 23 + Classes/FolderStructure/FileNode.php | 319 + Classes/FolderStructure/LinkNode.php | 219 + .../FolderStructure/LinkOrDirectoryNode.php | 89 + Classes/FolderStructure/NodeInterface.php | 69 + Classes/FolderStructure/RootNode.php | 96 + Classes/FolderStructure/RootNodeInterface.php | 21 + Classes/FolderStructure/StructureFacade.php | 63 + .../StructureFacadeInterface.php | 39 + Classes/Http/Application.php | 63 + Classes/Http/NotFoundRequestHandler.php | 39 + Classes/Middleware/AssetPublishing.php | 46 + Classes/Middleware/Installer.php | 162 + .../JavaScriptLanguageDomainProvider.php | 72 + Classes/Middleware/Maintenance.php | 333 + Classes/Report/EnvironmentStatusReport.php | 143 + Classes/Report/InstallStatusReport.php | 333 + Classes/Report/SecurityStatusReport.php | 118 + Classes/Service/ClearCacheService.php | 81 + Classes/Service/ClearTableService.php | 119 + Classes/Service/CoreUpdateService.php | 531 ++ Classes/Service/CoreVersionService.php | 214 + Classes/Service/EnableFileService.php | 234 + Classes/Service/Exception.php | 21 + ...gurationDirectoryDoesNotExistException.php | 25 + ...onfigurationFileAlreadyExistsException.php | 25 + .../Exception/CoreVersionServiceException.php | 25 + .../Exception/RemoteFetchException.php | 23 + .../TemplateFileChangedException.php | 25 + Classes/Service/LateBootService.php | 37 + .../LocalConfigurationValueService.php | 334 + .../Service/Session/FileSessionHandler.php | 280 + .../Service/Session/RedisSessionHandler.php | 127 + Classes/Service/SessionService.php | 460 ++ Classes/Service/SetupDatabaseService.php | 728 ++ Classes/Service/SetupService.php | 580 ++ .../SilentTemplateFileUpgradeService.php | 73 + Classes/Service/Typo3tempFileService.php | 193 + .../WebServerConfigurationFileService.php | 173 + Classes/ServiceProvider.php | 444 ++ Classes/SystemEnvironment/Check.php | 879 +++ Classes/SystemEnvironment/CheckInterface.php | 36 + Classes/SystemEnvironment/DatabaseCheck.php | 302 + .../DatabaseCheck/Driver/AbstractDriver.php | 66 + .../DatabaseCheck/Driver/Mysqli.php | 70 + .../DatabaseCheck/Driver/PDOPgSql.php | 23 + .../DatabaseCheck/Driver/PDOSqlite.php | 23 + .../DatabaseCheck/Driver/PdoMysql.php | 44 + .../Platform/AbstractPlatform.php | 83 + .../DatabaseCheck/Platform/MySql.php | 294 + .../Platform/PlatformCheckInterface.php | 43 + .../DatabaseCheck/Platform/PostgreSql.php | 234 + .../DatabaseCheck/Platform/Sqlite.php | 98 + .../ContentSecurityPolicyDirective.php | 83 + .../ContentSecurityPolicyHeader.php | 77 + .../ServerResponse/FileDeclaration.php | 248 + .../ServerResponse/FileLocation.php | 48 + .../ServerResponse/ServerResponseCheck.php | 378 + .../ServerResponse/StatusMessage.php | 47 + Classes/SystemEnvironment/SetupCheck.php | 282 + Classes/UpgradeAnalysis/DocumentationFile.php | 240 + Classes/ViewHelpers/Exception.php | 21 + .../ViewHelpers/Format/CropTextViewHelper.php | 47 + .../ViewHelpers/Format/NoSpaceViewHelper.php | 46 + .../Format/PhpErrorCodeViewHelper.php | 81 + Classes/ViewHelpers/PhpInfoViewHelper.php | 80 + Classes/WebserverType.php | 69 + Configuration/Backend/Modules.php | 90 + Configuration/Backend/Routes.php | 26 + .../AbstractMethodImplementationMatcher.php | 36 + .../Php/ArrayDimensionMatcher.php | 1376 ++++ .../Php/ArrayGlobalMatcher.php | 92 + .../Php/ClassConstantMatcher.php | 667 ++ .../ExtensionScanner/Php/ClassNameMatcher.php | 2833 +++++++ .../ExtensionScanner/Php/ConstantMatcher.php | 292 + .../Php/ConstructorArgumentMatcher.php | 42 + .../Php/FunctionCallMatcher.php | 33 + .../Php/InterfaceMethodChangedMatcher.php | 18 + .../Php/MethodAnnotationMatcher.php | 17 + .../Php/MethodArgumentDroppedMatcher.php | 461 ++ .../MethodArgumentDroppedStaticMatcher.php | 106 + .../Php/MethodArgumentRequiredMatcher.php | 74 + .../MethodArgumentRequiredStaticMatcher.php | 45 + .../Php/MethodArgumentUnusedMatcher.php | 43 + .../Php/MethodCallArgumentValueMatcher.php | 40 + .../Php/MethodCallMatcher.php | 7017 +++++++++++++++++ .../Php/MethodCallStaticMatcher.php | 1881 +++++ .../Php/PropertyAnnotationMatcher.php | 45 + .../Php/PropertyExistsStaticMatcher.php | 11 + .../Php/PropertyProtectedMatcher.php | 1440 ++++ .../Php/PropertyPublicMatcher.php | 1254 +++ .../Php/ScalarStringMatcher.php | 10 + Configuration/Icons.php | 20 + Configuration/JavaScriptModules.php | 26 + .../be_groups_advanced_editor.yaml | 227 + .../PermissionPreset/be_groups_editor.yaml | 139 + Configuration/Services.yaml | 45 + LICENSE.txt | 339 + README.rst | 18 + .../fileadmin-temp-htaccess | 15 + .../fileadmin-temp-index.html | 7 + ...min-user_upload-temp-importexport-htaccess | 15 + .../resources-root-htaccess | 37 + .../root-htaccess | 394 + .../root-web-config | 85 + .../typo3temp-var-htaccess | 15 + Resources/Private/Font/README.txt | 137 + Resources/Private/Font/vera.ttf | Bin 0 -> 65932 bytes .../Private/Language/Modules/environment.xlf | 17 + .../Private/Language/Modules/maintenance.xlf | 17 + .../Private/Language/Modules/settings.xlf | 17 + .../Private/Language/Modules/upgrade.xlf | 17 + .../Private/Language/Report/locallang.xlf | 110 + Resources/Private/Layouts/Cards.fluid.html | 5 + .../Private/Partials/Card/Header.fluid.html | 17 + .../ConfigurationNotWritable.fluid.html | 10 + .../ExtensionForm.fluid.html | 195 + .../LocalConfiguration/SubSection.fluid.html | 259 + .../Settings/Presets/Cache.fluid.html | 70 + .../Settings/Presets/Cache/Custom.fluid.html | 38 + .../Presets/Cache/Database.fluid.html | 20 + .../Settings/Presets/Cache/File.fluid.html | 20 + .../Settings/Presets/Context.fluid.html | 46 + .../Presets/Context/Custom.fluid.html | 39 + .../Settings/Presets/Context/Debug.fluid.html | 21 + .../Settings/Presets/Context/Live.fluid.html | 21 + .../Settings/Presets/Image.fluid.html | 68 + .../Settings/Presets/Image/Custom.fluid.html | 40 + .../Presets/Image/GraphicsMagick.fluid.html | 32 + .../Presets/Image/ImageMagick6.fluid.html | 32 + .../Partials/Settings/Presets/Mail.fluid.html | 44 + .../Settings/Presets/Mail/Custom.fluid.html | 40 + .../Settings/Presets/Mail/Sendmail.fluid.html | 29 + .../Settings/Presets/Mail/Smtp.fluid.html | 22 + .../Presets/PasswordHashing.fluid.html | 46 + .../PasswordHashing/Argon2i.fluid.html | 35 + .../PasswordHashing/Argon2id.fluid.html | 33 + .../Presets/PasswordHashing/Bcrypt.fluid.html | 34 + .../Presets/PasswordHashing/Custom.fluid.html | 40 + .../Presets/PasswordHashing/Pbkdf2.fluid.html | 34 + .../Presets/PasswordHashing/Phpass.fluid.html | 34 + .../Partials/Settings/ReadonlyInfo.fluid.html | 12 + .../Upgrade/UpgradeDocs/PanelItem.fluid.html | 54 + .../Security/InstallToolLogin.fluid.html | 6 + .../Email/Security/InstallToolLogin.fluid.txt | 5 + .../InstallToolLoginAttempt.fluid.html | 6 + .../InstallToolLoginAttempt.fluid.txt | 5 + .../Templates/Environment/Cards.fluid.html | 62 + .../Environment/EnvironmentCheck.fluid.html | 5 + .../Environment/FolderStructure.fluid.html | 20 + .../Environment/ImageProcessing.fluid.html | 380 + .../Templates/Environment/MailTest.fluid.html | 45 + .../Templates/Environment/PhpInfo.fluid.html | 7 + .../Environment/SystemInformation.fluid.html | 62 + .../Templates/Installer/Init.fluid.html | 17 + .../Templates/Installer/MainLayout.fluid.html | 15 + .../Installer/ShowDatabaseConnect.fluid.html | 403 + .../Installer/ShowDatabaseData.fluid.html | 91 + .../Installer/ShowDatabaseSelect.fluid.html | 97 + .../ShowDefaultConfiguration.fluid.html | 116 + .../ShowEnvironmentAndFolders.fluid.html | 36 + .../ShowInstallerNotAvailable.fluid.html | 19 + .../Private/Templates/Layout/Init.fluid.html | 22 + .../Templates/Layout/MainLayout.fluid.html | 174 + .../ShowEnableInstallToolFile.fluid.html | 43 + .../Templates/Login/ShowLogin.fluid.html | 73 + .../Templates/Maintenance/Cards.fluid.html | 99 + .../Maintenance/ClearTables.fluid.html | 35 + .../ClearTypo3tempFiles.fluid.html | 36 + .../Maintenance/CreateAdmin.fluid.html | 78 + .../Maintenance/DatabaseAnalyzer.fluid.html | 55 + .../Maintenance/LanguagePacks.fluid.html | 33 + .../Maintenance/ReferenceIndex.fluid.html | 31 + .../Templates/Settings/Cards.fluid.html | 66 + .../ChangeInstallToolPassword.fluid.html | 39 + ...xtensionConfigurationGetContent.fluid.html | 31 + .../Settings/FeaturesGetContent.fluid.html | 41 + .../LocalConfigurationGetContent.fluid.html | 37 + .../Settings/PresetsGetContent.fluid.html | 30 + .../Settings/SystemMaintainer.fluid.html | 40 + .../Templates/Upgrade/Cards.fluid.html | 81 + .../Templates/Upgrade/CoreUpdate.fluid.html | 26 + .../Upgrade/ExtensionCompatTester.fluid.html | 16 + .../Upgrade/ExtensionScanner.fluid.html | 132 + .../Upgrade/TcaMigrationsCheck.fluid.html | 9 + ...gradeDocsGetChangelogForVersion.fluid.html | 15 + .../Upgrade/UpgradeDocsGetContent.fluid.html | 109 + .../Upgrade/UpgradeWizards.fluid.html | 122 + Resources/Public/Icons/Extension.svg | 1 + Resources/Public/Icons/favicon.ico | Bin 0 -> 16958 bytes .../Icons/module-install-environment.svg | 1 + .../Icons/module-install-maintenance.svg | 1 + .../Public/Icons/module-install-settings.svg | 1 + .../Public/Icons/module-install-upgrade.svg | 1 + Resources/Public/Icons/module-install.svg | 1 + .../modules/install-check-brokenextension.svg | 1 + .../Icons/modules/install-check-directory.svg | 1 + .../Icons/modules/install-check-extables.svg | 1 + .../Icons/modules/install-check-tca.svg | 1 + .../Icons/modules/install-clear-autoload.svg | 1 + .../Icons/modules/install-clear-cache.svg | 1 + .../Icons/modules/install-clear-database.svg | 1 + .../Icons/modules/install-clear-files.svg | 1 + .../Icons/modules/install-create-admin.svg | 1 + .../modules/install-database-analyze.svg | 1 + .../Icons/modules/install-documentation.svg | 1 + .../modules/install-extension-settings.svg | 1 + .../Icons/modules/install-manage-features.svg | 1 + .../Icons/modules/install-manage-language.svg | 1 + .../modules/install-manage-maintainer.svg | 1 + .../Icons/modules/install-manage-presets.svg | 1 + .../Icons/modules/install-manage-settings.svg | 1 + .../Public/Icons/modules/install-password.svg | 1 + .../Public/Icons/modules/install-php-info.svg | 1 + .../Icons/modules/install-reset-user.svg | 1 + .../Icons/modules/install-scan-extensions.svg | 1 + .../modules/install-show-environment.svg | 1 + .../modules/install-test-environment.svg | 1 + .../Icons/modules/install-test-image.svg | 1 + .../Icons/modules/install-test-mail.svg | 1 + .../Public/Icons/modules/install-update.svg | 1 + .../Public/Icons/modules/install-wizards.svg | 1 + Resources/Public/Images/Icon/SpinnerSmall.gif | Bin 0 -> 1727 bytes .../Images/TestInput/BackgroundCombine.jpg | Bin 0 -> 24960 bytes .../Images/TestInput/BackgroundOrange.gif | Bin 0 -> 404 bytes .../Images/TestInput/MaskBlackWhite.gif | Bin 0 -> 935 bytes .../Public/Images/TestInput/MaskCombine.jpg | Bin 0 -> 25009 bytes Resources/Public/Images/TestInput/Test.ai | 544 ++ Resources/Public/Images/TestInput/Test.avif | Bin 0 -> 6495 bytes Resources/Public/Images/TestInput/Test.gif | Bin 0 -> 21666 bytes Resources/Public/Images/TestInput/Test.jpg | Bin 0 -> 22835 bytes Resources/Public/Images/TestInput/Test.pdf | 446 ++ Resources/Public/Images/TestInput/Test.png | Bin 0 -> 20061 bytes Resources/Public/Images/TestInput/Test.svg | 1279 +++ Resources/Public/Images/TestInput/Test.tif | Bin 0 -> 58142 bytes Resources/Public/Images/TestInput/Test.webp | Bin 0 -> 16322 bytes .../Public/Images/TestInput/Transparent.gif | Bin 0 -> 16287 bytes .../Public/Images/TestInput/Transparent.png | Bin 0 -> 27618 bytes .../Public/Images/TestInput/Transparent.svg | 4 + .../Public/Images/TestReference/Combine-1.jpg | Bin 0 -> 11483 bytes .../Public/Images/TestReference/Combine-2.jpg | Bin 0 -> 18192 bytes .../Convert-avif-transparent.avif | Bin 0 -> 5619 bytes .../Images/TestReference/Convert-avif.avif | Bin 0 -> 6495 bytes .../TestReference/Convert-svg-transparent.png | Bin 0 -> 10543 bytes .../Convert-svg-transparent.webp | Bin 0 -> 3540 bytes .../Convert-webp-transparent.webp | Bin 0 -> 8888 bytes .../Images/TestReference/Convert-webp.webp | Bin 0 -> 16322 bytes .../Public/Images/TestReference/Font.gif | Bin 0 -> 732 bytes .../Images/TestReference/Gdlib-box.avif | Bin 0 -> 12479 bytes .../Public/Images/TestReference/Gdlib-box.png | Bin 0 -> 16578 bytes .../Images/TestReference/Gdlib-box.webp | Bin 0 -> 15502 bytes .../Images/TestReference/Gdlib-niceText.png | Bin 0 -> 7122 bytes .../Images/TestReference/Gdlib-shadow.png | Bin 0 -> 13945 bytes .../Images/TestReference/Gdlib-simple.png | Bin 0 -> 410 bytes .../Images/TestReference/Gdlib-text.png | Bin 0 -> 3185 bytes .../Public/Images/TestReference/Read-ai.jpg | Bin 0 -> 7399 bytes .../Public/Images/TestReference/Read-avif.jpg | Bin 0 -> 15032 bytes .../Public/Images/TestReference/Read-bmp.jpg | Bin 0 -> 13630 bytes .../Public/Images/TestReference/Read-gif.jpg | Bin 0 -> 13677 bytes .../Public/Images/TestReference/Read-jpg.jpg | Bin 0 -> 15032 bytes .../Public/Images/TestReference/Read-pdf.jpg | Bin 0 -> 16817 bytes .../Public/Images/TestReference/Read-png.jpg | Bin 0 -> 13630 bytes .../Public/Images/TestReference/Read-tga.jpg | Bin 0 -> 13630 bytes .../Public/Images/TestReference/Read-tif.jpg | Bin 0 -> 13630 bytes .../Public/Images/TestReference/Read-webp.jpg | Bin 0 -> 13630 bytes .../Public/Images/TestReference/Scale-gif.gif | Bin 0 -> 17623 bytes .../Public/Images/TestReference/Scale-jpg.jpg | Bin 0 -> 13938 bytes .../Public/Images/TestReference/Scale-png.png | Bin 0 -> 34604 bytes .../Images/TestReference/Write-avif.avif | Bin 0 -> 6495 bytes .../Public/Images/TestReference/Write-gif.gif | Bin 0 -> 22797 bytes .../Public/Images/TestReference/Write-png.png | Bin 0 -> 23857 bytes .../Images/TestReference/Write-webp.webp | Bin 0 -> 16322 bytes Resources/Public/Images/typo3_logo_orange.svg | 3 + .../Public/JavaScript/ajax/ajax-queue.js | 13 + Resources/Public/JavaScript/init-install.js | 13 + Resources/Public/JavaScript/init-installer.js | 13 + Resources/Public/JavaScript/install.js | 13 + Resources/Public/JavaScript/installer.js | 13 + .../module/abstract-inline-module.js | 13 + .../module/abstract-interactable-module.js | 13 + .../module/environment/environment-check.js | 13 + .../module/environment/folder-structure.js | 13 + .../module/environment/image-processing.js | 16 + .../module/environment/mail-test.js | 13 + .../JavaScript/module/environment/php-info.js | 13 + .../module/environment/system-information.js | 13 + .../JavaScript/module/maintenance/cache.js | 13 + .../module/maintenance/clear-tables.js | 13 + .../maintenance/clear-typo3temp-files.js | 13 + .../module/maintenance/create-admin.js | 13 + .../module/maintenance/database-analyzer.js | 27 + .../module/maintenance/dump-autoload.js | 13 + .../module/maintenance/language-packs.js | 13 + .../module/maintenance/reference-index.js | 25 + .../maintenance/reset-backend-user-uc.js | 13 + .../JavaScript/module/password-strength.js | 13 + .../settings/change-install-tool-password.js | 13 + .../settings/extension-configuration.js | 13 + .../JavaScript/module/settings/features.js | 13 + .../module/settings/local-configuration.js | 13 + .../JavaScript/module/settings/presets.js | 13 + .../module/settings/system-maintainer.js | 13 + .../JavaScript/module/upgrade/core-update.js | 13 + .../module/upgrade/extension-compat-tester.js | 13 + .../module/upgrade/extension-scanner.js | 13 + .../module/upgrade/tca-migrations-check.js | 13 + .../JavaScript/module/upgrade/upgrade-docs.js | 13 + .../module/upgrade/upgrade-wizards.js | 13 + .../JavaScript/renderable/flash-message.js | 13 + .../Public/JavaScript/renderable/info-box.js | 13 + .../JavaScript/renderable/language-packs.js | 13 + .../JavaScript/renderable/offset-group.js | 13 + .../Public/JavaScript/renderable/severity.js | 13 + .../JavaScript/renderable/wrap-group.js | 13 + Resources/Public/JavaScript/router.js | 13 + composer.json | 66 + 407 files changed, 51272 insertions(+) create mode 100644 .gitignore create mode 100644 Classes/Authentication/AuthenticationService.php create mode 100644 Classes/Command/BackendUserGroupType.php create mode 100644 Classes/Command/PasswordSetCommand.php create mode 100644 Classes/Command/SetupCommand.php create mode 100644 Classes/Command/SetupDefaultBackendUserGroupsCommand.php create mode 100644 Classes/Configuration/AbstractCustomPreset.php create mode 100644 Classes/Configuration/AbstractFeature.php create mode 100644 Classes/Configuration/AbstractPreset.php create mode 100644 Classes/Configuration/Cache/CacheFeature.php create mode 100644 Classes/Configuration/Cache/CustomCachePreset.php create mode 100644 Classes/Configuration/Cache/DatabaseCachePreset.php create mode 100644 Classes/Configuration/Cache/FileCachePreset.php create mode 100644 Classes/Configuration/Context/ContextFeature.php create mode 100644 Classes/Configuration/Context/CustomPreset.php create mode 100644 Classes/Configuration/Context/DebugPreset.php create mode 100644 Classes/Configuration/Context/LivePreset.php create mode 100644 Classes/Configuration/CustomPresetInterface.php create mode 100644 Classes/Configuration/Exception.php create mode 100644 Classes/Configuration/FeatureInterface.php create mode 100644 Classes/Configuration/FeatureManager.php create mode 100644 Classes/Configuration/Image/AbstractImagePreset.php create mode 100644 Classes/Configuration/Image/CustomPreset.php create mode 100644 Classes/Configuration/Image/GraphicsMagickPreset.php create mode 100644 Classes/Configuration/Image/ImageFeature.php create mode 100644 Classes/Configuration/Image/ImageMagick6Preset.php create mode 100644 Classes/Configuration/Mail/CustomPreset.php create mode 100644 Classes/Configuration/Mail/MailFeature.php create mode 100644 Classes/Configuration/Mail/SendmailPreset.php create mode 100644 Classes/Configuration/Mail/SmtpPreset.php create mode 100644 Classes/Configuration/PasswordHashing/Argon2iPreset.php create mode 100644 Classes/Configuration/PasswordHashing/Argon2idPreset.php create mode 100644 Classes/Configuration/PasswordHashing/BcryptPreset.php create mode 100644 Classes/Configuration/PasswordHashing/CustomPreset.php create mode 100644 Classes/Configuration/PasswordHashing/PasswordHashingFeature.php create mode 100644 Classes/Configuration/PasswordHashing/Pbkdf2Preset.php create mode 100644 Classes/Configuration/PasswordHashing/PhpassPreset.php create mode 100644 Classes/Configuration/PresetInterface.php create mode 100644 Classes/Controller/AbstractController.php create mode 100644 Classes/Controller/BackendModuleController.php create mode 100644 Classes/Controller/ControllerTrait.php create mode 100644 Classes/Controller/EntryPointRedirectController.php create mode 100644 Classes/Controller/EnvironmentController.php create mode 100644 Classes/Controller/IconController.php create mode 100644 Classes/Controller/InstallerController.php create mode 100644 Classes/Controller/LayoutController.php create mode 100644 Classes/Controller/LoginController.php create mode 100644 Classes/Controller/MaintenanceController.php create mode 100644 Classes/Controller/ServerResponseCheckController.php create mode 100644 Classes/Controller/SettingsController.php create mode 100644 Classes/Controller/UpgradeController.php create mode 100644 Classes/CoreVersion/CoreRelease.php create mode 100644 Classes/CoreVersion/MaintenanceWindow.php create mode 100644 Classes/CoreVersion/MajorRelease.php create mode 100644 Classes/Database/PermissionsCheck.php create mode 100644 Classes/Exception.php create mode 100644 Classes/ExtensionScanner/CodeScannerInterface.php create mode 100644 Classes/ExtensionScanner/Php/CodeStatistics.php create mode 100644 Classes/ExtensionScanner/Php/GeneratorClassesResolver.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/AbstractCoreMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/AbstractMethodImplementationMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/ArrayDimensionMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/ArrayGlobalMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/ClassConstantMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/ClassNameMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/ConstantMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/ConstructorArgumentMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/FunctionCallMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/InterfaceMethodChangedMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/MethodAnnotationMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/MethodArgumentDroppedMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/MethodArgumentDroppedStaticMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/MethodArgumentRequiredMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/MethodArgumentRequiredStaticMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/MethodArgumentUnusedMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/MethodCallArgumentValueMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/MethodCallMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/MethodCallStaticMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/PropertyAnnotationMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/PropertyExistsStaticMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/PropertyProtectedMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/PropertyPublicMatcher.php create mode 100644 Classes/ExtensionScanner/Php/Matcher/ScalarStringMatcher.php create mode 100644 Classes/ExtensionScanner/Php/MatcherFactory.php create mode 100644 Classes/Factory/ImportMapFactory.php create mode 100644 Classes/FolderStructure/AbstractNode.php create mode 100644 Classes/FolderStructure/DefaultFactory.php create mode 100644 Classes/FolderStructure/DefaultPermissionsCheck.php create mode 100644 Classes/FolderStructure/DirectoryNode.php create mode 100644 Classes/FolderStructure/Exception.php create mode 100644 Classes/FolderStructure/Exception/InvalidArgumentException.php create mode 100644 Classes/FolderStructure/Exception/RootNodeException.php create mode 100644 Classes/FolderStructure/FileNode.php create mode 100644 Classes/FolderStructure/LinkNode.php create mode 100644 Classes/FolderStructure/LinkOrDirectoryNode.php create mode 100644 Classes/FolderStructure/NodeInterface.php create mode 100644 Classes/FolderStructure/RootNode.php create mode 100644 Classes/FolderStructure/RootNodeInterface.php create mode 100644 Classes/FolderStructure/StructureFacade.php create mode 100644 Classes/FolderStructure/StructureFacadeInterface.php create mode 100644 Classes/Http/Application.php create mode 100644 Classes/Http/NotFoundRequestHandler.php create mode 100644 Classes/Middleware/AssetPublishing.php create mode 100644 Classes/Middleware/Installer.php create mode 100644 Classes/Middleware/JavaScriptLanguageDomainProvider.php create mode 100644 Classes/Middleware/Maintenance.php create mode 100644 Classes/Report/EnvironmentStatusReport.php create mode 100644 Classes/Report/InstallStatusReport.php create mode 100644 Classes/Report/SecurityStatusReport.php create mode 100644 Classes/Service/ClearCacheService.php create mode 100644 Classes/Service/ClearTableService.php create mode 100644 Classes/Service/CoreUpdateService.php create mode 100644 Classes/Service/CoreVersionService.php create mode 100644 Classes/Service/EnableFileService.php create mode 100644 Classes/Service/Exception.php create mode 100644 Classes/Service/Exception/ConfigurationDirectoryDoesNotExistException.php create mode 100644 Classes/Service/Exception/ConfigurationFileAlreadyExistsException.php create mode 100644 Classes/Service/Exception/CoreVersionServiceException.php create mode 100644 Classes/Service/Exception/RemoteFetchException.php create mode 100644 Classes/Service/Exception/TemplateFileChangedException.php create mode 100644 Classes/Service/LateBootService.php create mode 100644 Classes/Service/LocalConfigurationValueService.php create mode 100644 Classes/Service/Session/FileSessionHandler.php create mode 100644 Classes/Service/Session/RedisSessionHandler.php create mode 100644 Classes/Service/SessionService.php create mode 100644 Classes/Service/SetupDatabaseService.php create mode 100644 Classes/Service/SetupService.php create mode 100644 Classes/Service/SilentTemplateFileUpgradeService.php create mode 100644 Classes/Service/Typo3tempFileService.php create mode 100644 Classes/Service/WebServerConfigurationFileService.php create mode 100644 Classes/ServiceProvider.php create mode 100644 Classes/SystemEnvironment/Check.php create mode 100644 Classes/SystemEnvironment/CheckInterface.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Driver/AbstractDriver.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Driver/Mysqli.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Driver/PDOPgSql.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Driver/PDOSqlite.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Driver/PdoMysql.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Platform/AbstractPlatform.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Platform/MySql.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Platform/PlatformCheckInterface.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Platform/PostgreSql.php create mode 100644 Classes/SystemEnvironment/DatabaseCheck/Platform/Sqlite.php create mode 100644 Classes/SystemEnvironment/ServerResponse/ContentSecurityPolicyDirective.php create mode 100644 Classes/SystemEnvironment/ServerResponse/ContentSecurityPolicyHeader.php create mode 100644 Classes/SystemEnvironment/ServerResponse/FileDeclaration.php create mode 100644 Classes/SystemEnvironment/ServerResponse/FileLocation.php create mode 100644 Classes/SystemEnvironment/ServerResponse/ServerResponseCheck.php create mode 100644 Classes/SystemEnvironment/ServerResponse/StatusMessage.php create mode 100644 Classes/SystemEnvironment/SetupCheck.php create mode 100644 Classes/UpgradeAnalysis/DocumentationFile.php create mode 100644 Classes/ViewHelpers/Exception.php create mode 100644 Classes/ViewHelpers/Format/CropTextViewHelper.php create mode 100644 Classes/ViewHelpers/Format/NoSpaceViewHelper.php create mode 100644 Classes/ViewHelpers/Format/PhpErrorCodeViewHelper.php create mode 100644 Classes/ViewHelpers/PhpInfoViewHelper.php create mode 100644 Classes/WebserverType.php create mode 100644 Configuration/Backend/Modules.php create mode 100644 Configuration/Backend/Routes.php create mode 100644 Configuration/ExtensionScanner/Php/AbstractMethodImplementationMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/ArrayGlobalMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/ClassConstantMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/ClassNameMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/ConstantMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/ConstructorArgumentMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/FunctionCallMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/InterfaceMethodChangedMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/MethodAnnotationMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/MethodArgumentDroppedMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/MethodArgumentDroppedStaticMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/MethodArgumentRequiredStaticMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/MethodArgumentUnusedMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/MethodCallArgumentValueMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/MethodCallMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/PropertyAnnotationMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/PropertyExistsStaticMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/PropertyProtectedMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/PropertyPublicMatcher.php create mode 100644 Configuration/ExtensionScanner/Php/ScalarStringMatcher.php create mode 100644 Configuration/Icons.php create mode 100644 Configuration/JavaScriptModules.php create mode 100644 Configuration/PermissionPreset/be_groups_advanced_editor.yaml create mode 100644 Configuration/PermissionPreset/be_groups_editor.yaml create mode 100644 Configuration/Services.yaml create mode 100644 LICENSE.txt create mode 100644 README.rst create mode 100644 Resources/Private/FolderStructureTemplateFiles/fileadmin-temp-htaccess create mode 100644 Resources/Private/FolderStructureTemplateFiles/fileadmin-temp-index.html create mode 100644 Resources/Private/FolderStructureTemplateFiles/fileadmin-user_upload-temp-importexport-htaccess create mode 100644 Resources/Private/FolderStructureTemplateFiles/resources-root-htaccess create mode 100644 Resources/Private/FolderStructureTemplateFiles/root-htaccess create mode 100644 Resources/Private/FolderStructureTemplateFiles/root-web-config create mode 100644 Resources/Private/FolderStructureTemplateFiles/typo3temp-var-htaccess create mode 100644 Resources/Private/Font/README.txt create mode 100644 Resources/Private/Font/vera.ttf create mode 100644 Resources/Private/Language/Modules/environment.xlf create mode 100644 Resources/Private/Language/Modules/maintenance.xlf create mode 100644 Resources/Private/Language/Modules/settings.xlf create mode 100644 Resources/Private/Language/Modules/upgrade.xlf create mode 100644 Resources/Private/Language/Report/locallang.xlf create mode 100644 Resources/Private/Layouts/Cards.fluid.html create mode 100644 Resources/Private/Partials/Card/Header.fluid.html create mode 100644 Resources/Private/Partials/Generic/ConfigurationNotWritable.fluid.html create mode 100644 Resources/Private/Partials/Settings/ExtensionConfiguration/ExtensionForm.fluid.html create mode 100644 Resources/Private/Partials/Settings/LocalConfiguration/SubSection.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Cache.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Cache/Custom.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Cache/Database.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Cache/File.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Context.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Context/Custom.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Context/Debug.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Context/Live.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Image.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Image/Custom.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Image/GraphicsMagick.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Image/ImageMagick6.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Mail.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Mail/Custom.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Mail/Sendmail.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/Mail/Smtp.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/PasswordHashing.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/PasswordHashing/Argon2i.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/PasswordHashing/Argon2id.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/PasswordHashing/Bcrypt.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/PasswordHashing/Custom.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/PasswordHashing/Pbkdf2.fluid.html create mode 100644 Resources/Private/Partials/Settings/Presets/PasswordHashing/Phpass.fluid.html create mode 100644 Resources/Private/Partials/Settings/ReadonlyInfo.fluid.html create mode 100644 Resources/Private/Partials/Upgrade/UpgradeDocs/PanelItem.fluid.html create mode 100644 Resources/Private/Templates/Email/Security/InstallToolLogin.fluid.html create mode 100644 Resources/Private/Templates/Email/Security/InstallToolLogin.fluid.txt create mode 100644 Resources/Private/Templates/Email/Security/InstallToolLoginAttempt.fluid.html create mode 100644 Resources/Private/Templates/Email/Security/InstallToolLoginAttempt.fluid.txt create mode 100644 Resources/Private/Templates/Environment/Cards.fluid.html create mode 100644 Resources/Private/Templates/Environment/EnvironmentCheck.fluid.html create mode 100644 Resources/Private/Templates/Environment/FolderStructure.fluid.html create mode 100644 Resources/Private/Templates/Environment/ImageProcessing.fluid.html create mode 100644 Resources/Private/Templates/Environment/MailTest.fluid.html create mode 100644 Resources/Private/Templates/Environment/PhpInfo.fluid.html create mode 100644 Resources/Private/Templates/Environment/SystemInformation.fluid.html create mode 100644 Resources/Private/Templates/Installer/Init.fluid.html create mode 100644 Resources/Private/Templates/Installer/MainLayout.fluid.html create mode 100644 Resources/Private/Templates/Installer/ShowDatabaseConnect.fluid.html create mode 100644 Resources/Private/Templates/Installer/ShowDatabaseData.fluid.html create mode 100644 Resources/Private/Templates/Installer/ShowDatabaseSelect.fluid.html create mode 100644 Resources/Private/Templates/Installer/ShowDefaultConfiguration.fluid.html create mode 100644 Resources/Private/Templates/Installer/ShowEnvironmentAndFolders.fluid.html create mode 100644 Resources/Private/Templates/Installer/ShowInstallerNotAvailable.fluid.html create mode 100644 Resources/Private/Templates/Layout/Init.fluid.html create mode 100644 Resources/Private/Templates/Layout/MainLayout.fluid.html create mode 100644 Resources/Private/Templates/Login/ShowEnableInstallToolFile.fluid.html create mode 100644 Resources/Private/Templates/Login/ShowLogin.fluid.html create mode 100644 Resources/Private/Templates/Maintenance/Cards.fluid.html create mode 100644 Resources/Private/Templates/Maintenance/ClearTables.fluid.html create mode 100644 Resources/Private/Templates/Maintenance/ClearTypo3tempFiles.fluid.html create mode 100644 Resources/Private/Templates/Maintenance/CreateAdmin.fluid.html create mode 100644 Resources/Private/Templates/Maintenance/DatabaseAnalyzer.fluid.html create mode 100644 Resources/Private/Templates/Maintenance/LanguagePacks.fluid.html create mode 100644 Resources/Private/Templates/Maintenance/ReferenceIndex.fluid.html create mode 100644 Resources/Private/Templates/Settings/Cards.fluid.html create mode 100644 Resources/Private/Templates/Settings/ChangeInstallToolPassword.fluid.html create mode 100644 Resources/Private/Templates/Settings/ExtensionConfigurationGetContent.fluid.html create mode 100644 Resources/Private/Templates/Settings/FeaturesGetContent.fluid.html create mode 100644 Resources/Private/Templates/Settings/LocalConfigurationGetContent.fluid.html create mode 100644 Resources/Private/Templates/Settings/PresetsGetContent.fluid.html create mode 100644 Resources/Private/Templates/Settings/SystemMaintainer.fluid.html create mode 100644 Resources/Private/Templates/Upgrade/Cards.fluid.html create mode 100644 Resources/Private/Templates/Upgrade/CoreUpdate.fluid.html create mode 100644 Resources/Private/Templates/Upgrade/ExtensionCompatTester.fluid.html create mode 100644 Resources/Private/Templates/Upgrade/ExtensionScanner.fluid.html create mode 100644 Resources/Private/Templates/Upgrade/TcaMigrationsCheck.fluid.html create mode 100644 Resources/Private/Templates/Upgrade/UpgradeDocsGetChangelogForVersion.fluid.html create mode 100644 Resources/Private/Templates/Upgrade/UpgradeDocsGetContent.fluid.html create mode 100644 Resources/Private/Templates/Upgrade/UpgradeWizards.fluid.html create mode 100644 Resources/Public/Icons/Extension.svg create mode 100644 Resources/Public/Icons/favicon.ico create mode 100644 Resources/Public/Icons/module-install-environment.svg create mode 100644 Resources/Public/Icons/module-install-maintenance.svg create mode 100644 Resources/Public/Icons/module-install-settings.svg create mode 100644 Resources/Public/Icons/module-install-upgrade.svg create mode 100644 Resources/Public/Icons/module-install.svg create mode 100644 Resources/Public/Icons/modules/install-check-brokenextension.svg create mode 100644 Resources/Public/Icons/modules/install-check-directory.svg create mode 100644 Resources/Public/Icons/modules/install-check-extables.svg create mode 100644 Resources/Public/Icons/modules/install-check-tca.svg create mode 100644 Resources/Public/Icons/modules/install-clear-autoload.svg create mode 100644 Resources/Public/Icons/modules/install-clear-cache.svg create mode 100644 Resources/Public/Icons/modules/install-clear-database.svg create mode 100644 Resources/Public/Icons/modules/install-clear-files.svg create mode 100644 Resources/Public/Icons/modules/install-create-admin.svg create mode 100644 Resources/Public/Icons/modules/install-database-analyze.svg create mode 100644 Resources/Public/Icons/modules/install-documentation.svg create mode 100644 Resources/Public/Icons/modules/install-extension-settings.svg create mode 100644 Resources/Public/Icons/modules/install-manage-features.svg create mode 100644 Resources/Public/Icons/modules/install-manage-language.svg create mode 100644 Resources/Public/Icons/modules/install-manage-maintainer.svg create mode 100644 Resources/Public/Icons/modules/install-manage-presets.svg create mode 100644 Resources/Public/Icons/modules/install-manage-settings.svg create mode 100644 Resources/Public/Icons/modules/install-password.svg create mode 100644 Resources/Public/Icons/modules/install-php-info.svg create mode 100644 Resources/Public/Icons/modules/install-reset-user.svg create mode 100644 Resources/Public/Icons/modules/install-scan-extensions.svg create mode 100644 Resources/Public/Icons/modules/install-show-environment.svg create mode 100644 Resources/Public/Icons/modules/install-test-environment.svg create mode 100644 Resources/Public/Icons/modules/install-test-image.svg create mode 100644 Resources/Public/Icons/modules/install-test-mail.svg create mode 100644 Resources/Public/Icons/modules/install-update.svg create mode 100644 Resources/Public/Icons/modules/install-wizards.svg create mode 100644 Resources/Public/Images/Icon/SpinnerSmall.gif create mode 100644 Resources/Public/Images/TestInput/BackgroundCombine.jpg create mode 100644 Resources/Public/Images/TestInput/BackgroundOrange.gif create mode 100644 Resources/Public/Images/TestInput/MaskBlackWhite.gif create mode 100644 Resources/Public/Images/TestInput/MaskCombine.jpg create mode 100644 Resources/Public/Images/TestInput/Test.ai create mode 100644 Resources/Public/Images/TestInput/Test.avif create mode 100644 Resources/Public/Images/TestInput/Test.gif create mode 100644 Resources/Public/Images/TestInput/Test.jpg create mode 100644 Resources/Public/Images/TestInput/Test.pdf create mode 100644 Resources/Public/Images/TestInput/Test.png create mode 100644 Resources/Public/Images/TestInput/Test.svg create mode 100644 Resources/Public/Images/TestInput/Test.tif create mode 100644 Resources/Public/Images/TestInput/Test.webp create mode 100644 Resources/Public/Images/TestInput/Transparent.gif create mode 100644 Resources/Public/Images/TestInput/Transparent.png create mode 100644 Resources/Public/Images/TestInput/Transparent.svg create mode 100644 Resources/Public/Images/TestReference/Combine-1.jpg create mode 100644 Resources/Public/Images/TestReference/Combine-2.jpg create mode 100644 Resources/Public/Images/TestReference/Convert-avif-transparent.avif create mode 100644 Resources/Public/Images/TestReference/Convert-avif.avif create mode 100644 Resources/Public/Images/TestReference/Convert-svg-transparent.png create mode 100644 Resources/Public/Images/TestReference/Convert-svg-transparent.webp create mode 100644 Resources/Public/Images/TestReference/Convert-webp-transparent.webp create mode 100644 Resources/Public/Images/TestReference/Convert-webp.webp create mode 100644 Resources/Public/Images/TestReference/Font.gif create mode 100644 Resources/Public/Images/TestReference/Gdlib-box.avif create mode 100644 Resources/Public/Images/TestReference/Gdlib-box.png create mode 100644 Resources/Public/Images/TestReference/Gdlib-box.webp create mode 100644 Resources/Public/Images/TestReference/Gdlib-niceText.png create mode 100644 Resources/Public/Images/TestReference/Gdlib-shadow.png create mode 100644 Resources/Public/Images/TestReference/Gdlib-simple.png create mode 100644 Resources/Public/Images/TestReference/Gdlib-text.png create mode 100644 Resources/Public/Images/TestReference/Read-ai.jpg create mode 100644 Resources/Public/Images/TestReference/Read-avif.jpg create mode 100644 Resources/Public/Images/TestReference/Read-bmp.jpg create mode 100644 Resources/Public/Images/TestReference/Read-gif.jpg create mode 100644 Resources/Public/Images/TestReference/Read-jpg.jpg create mode 100644 Resources/Public/Images/TestReference/Read-pdf.jpg create mode 100644 Resources/Public/Images/TestReference/Read-png.jpg create mode 100644 Resources/Public/Images/TestReference/Read-tga.jpg create mode 100644 Resources/Public/Images/TestReference/Read-tif.jpg create mode 100644 Resources/Public/Images/TestReference/Read-webp.jpg create mode 100644 Resources/Public/Images/TestReference/Scale-gif.gif create mode 100644 Resources/Public/Images/TestReference/Scale-jpg.jpg create mode 100644 Resources/Public/Images/TestReference/Scale-png.png create mode 100644 Resources/Public/Images/TestReference/Write-avif.avif create mode 100644 Resources/Public/Images/TestReference/Write-gif.gif create mode 100644 Resources/Public/Images/TestReference/Write-png.png create mode 100644 Resources/Public/Images/TestReference/Write-webp.webp create mode 100644 Resources/Public/Images/typo3_logo_orange.svg create mode 100644 Resources/Public/JavaScript/ajax/ajax-queue.js create mode 100644 Resources/Public/JavaScript/init-install.js create mode 100644 Resources/Public/JavaScript/init-installer.js create mode 100644 Resources/Public/JavaScript/install.js create mode 100644 Resources/Public/JavaScript/installer.js create mode 100644 Resources/Public/JavaScript/module/abstract-inline-module.js create mode 100644 Resources/Public/JavaScript/module/abstract-interactable-module.js create mode 100644 Resources/Public/JavaScript/module/environment/environment-check.js create mode 100644 Resources/Public/JavaScript/module/environment/folder-structure.js create mode 100644 Resources/Public/JavaScript/module/environment/image-processing.js create mode 100644 Resources/Public/JavaScript/module/environment/mail-test.js create mode 100644 Resources/Public/JavaScript/module/environment/php-info.js create mode 100644 Resources/Public/JavaScript/module/environment/system-information.js create mode 100644 Resources/Public/JavaScript/module/maintenance/cache.js create mode 100644 Resources/Public/JavaScript/module/maintenance/clear-tables.js create mode 100644 Resources/Public/JavaScript/module/maintenance/clear-typo3temp-files.js create mode 100644 Resources/Public/JavaScript/module/maintenance/create-admin.js create mode 100644 Resources/Public/JavaScript/module/maintenance/database-analyzer.js create mode 100644 Resources/Public/JavaScript/module/maintenance/dump-autoload.js create mode 100644 Resources/Public/JavaScript/module/maintenance/language-packs.js create mode 100644 Resources/Public/JavaScript/module/maintenance/reference-index.js create mode 100644 Resources/Public/JavaScript/module/maintenance/reset-backend-user-uc.js create mode 100644 Resources/Public/JavaScript/module/password-strength.js create mode 100644 Resources/Public/JavaScript/module/settings/change-install-tool-password.js create mode 100644 Resources/Public/JavaScript/module/settings/extension-configuration.js create mode 100644 Resources/Public/JavaScript/module/settings/features.js create mode 100644 Resources/Public/JavaScript/module/settings/local-configuration.js create mode 100644 Resources/Public/JavaScript/module/settings/presets.js create mode 100644 Resources/Public/JavaScript/module/settings/system-maintainer.js create mode 100644 Resources/Public/JavaScript/module/upgrade/core-update.js create mode 100644 Resources/Public/JavaScript/module/upgrade/extension-compat-tester.js create mode 100644 Resources/Public/JavaScript/module/upgrade/extension-scanner.js create mode 100644 Resources/Public/JavaScript/module/upgrade/tca-migrations-check.js create mode 100644 Resources/Public/JavaScript/module/upgrade/upgrade-docs.js create mode 100644 Resources/Public/JavaScript/module/upgrade/upgrade-wizards.js create mode 100644 Resources/Public/JavaScript/renderable/flash-message.js create mode 100644 Resources/Public/JavaScript/renderable/info-box.js create mode 100644 Resources/Public/JavaScript/renderable/language-packs.js create mode 100644 Resources/Public/JavaScript/renderable/offset-group.js create mode 100644 Resources/Public/JavaScript/renderable/severity.js create mode 100644 Resources/Public/JavaScript/renderable/wrap-group.js create mode 100644 Resources/Public/JavaScript/router.js create mode 100644 composer.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/Classes/Authentication/AuthenticationService.php b/Classes/Authentication/AuthenticationService.php new file mode 100644 index 0000000..684c690 --- /dev/null +++ b/Classes/Authentication/AuthenticationService.php @@ -0,0 +1,173 @@ +get($installToolPassword, 'BE'); + // @todo: This code should check required hash updates and update the hash if needed + $validPassword = $hashInstance->checkPassword($password, $installToolPassword); + } catch (InvalidPasswordHashException $e) { + $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__); + $logger->error( + 'Invalid install tool password hash specified in "BE/installToolPassword" configuration.', + ['exceptionMessage' => $e->getMessage()] + ); + } + } + if ($validPassword) { + $session->setAuthorized(); + $this->sendLoginSuccessfulMail($request); + return true; + } + $this->sendLoginFailedMail($request); + return false; + } + + /** + * If install tool login mail is set, send a mail for a successful login. + */ + protected function sendLoginSuccessfulMail(ServerRequestInterface $request): void + { + $warningEmailAddress = $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr']; + if (!$warningEmailAddress) { + return; + } + $email = $this->emailFactory->createWithOverrides( + [20 => 'EXT:install/Resources/Private/Templates/Email/'], + [], + [], + $request, + ); + $email + ->to($warningEmailAddress) + ->subject('Install Tool Login at \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\'') + ->from(new Address($this->getSenderEmailAddress(), $this->getSenderEmailName())) + ->setTemplate('Security/InstallToolLogin'); + $this->sendEmail($email); + } + + /** + * If install tool login mail is set, send a mail for a failed login. + */ + protected function sendLoginFailedMail(ServerRequestInterface $request): void + { + $warningEmailAddress = $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr']; + if (!$warningEmailAddress) { + return; + } + $formValues = $request->getParsedBody()['install'] ?? $request->getQueryParams()['install'] ?? null; + $email = $this->emailFactory->createWithOverrides( + [20 => 'EXT:install/Resources/Private/Templates/Email/'], + [], + [], + $request, + ); + $email + ->to($warningEmailAddress) + ->subject('Install Tool Login ATTEMPT at \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\'') + ->from(new Address($this->getSenderEmailAddress(), $this->getSenderEmailName())) + ->setTemplate('Security/InstallToolLoginAttempt'); + $this->sendEmail($email); + } + + /** + * Sends an email and gracefully logs if the mail could not be sent due to configuration errors. + * + * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface + */ + protected function sendEmail(RawMessage $email): void + { + try { + $this->mailer->send($email); + } catch (TransportException $e) { + $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__); + $logger->warning('Could not send notification email to ' . $this->getSenderEmailAddress() . ' due to mailer settings error', [ + 'recipientList' => $this->getSenderEmailAddress(), + 'exception' => $e, + ]); + } catch (RfcComplianceException $e) { + $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__); + $logger->warning('Could not send notification email to ' . $this->getSenderEmailAddress() . ' due to invalid email address', [ + 'recipientList' => $this->getSenderEmailAddress(), + 'exception' => $e, + ]); + } + } + + /** + * 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 MailUtility::getSystemFromAddress(); + } + + /** + * 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 MailUtility::getSystemFromName() ?: 'TYPO3 CMS install tool'; + } +} diff --git a/Classes/Command/BackendUserGroupType.php b/Classes/Command/BackendUserGroupType.php new file mode 100644 index 0000000..385fecd --- /dev/null +++ b/Classes/Command/BackendUserGroupType.php @@ -0,0 +1,63 @@ +value; + } + + return $groups; + } + + /** + * Returns all but the "ALL|NONE" special type + * + * @return array + */ + public function getActualUserGroupTypes(): array + { + $allGroups = self::cases(); + $specificUserGroups = []; + foreach ($allGroups as $specificGroup) { + if (in_array($specificGroup->name, ['ALL', 'NONE'], true)) { + continue; + } + + $specificUserGroups[$specificGroup->name] = $specificGroup->value; + } + + return $specificUserGroups; + } +} diff --git a/Classes/Command/PasswordSetCommand.php b/Classes/Command/PasswordSetCommand.php new file mode 100644 index 0000000..63a2d35 --- /dev/null +++ b/Classes/Command/PasswordSetCommand.php @@ -0,0 +1,164 @@ +addOption( + 'password-length', + 'p', + InputOption::VALUE_OPTIONAL, + 'Specify the length of auto-generated passwords.', + ) + ->addOption( + 'dry-run', + 'd', + InputOption::VALUE_NONE, + 'If this option is set, the password would only be shown but not saved in settings. This also reveals the resulting hash.' + ); + } + + /** + * Creates an install-tool password (either auto-generated or manual) and stores the hash (if not --dry-run) + * + * @throws InvalidPasswordHashException + * @throws InvalidPasswordRulesException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + if (!Bootstrap::checkIfEssentialConfigurationExists($this->configurationManager)) { + $io->error('Setting an Install Tool password requires a working installation (configuration files are missing).'); + return Command::FAILURE; + } + + $currentSettingsWithoutAdditionalParsing = $this->configurationManager->getMergedLocalConfiguration(); + if (($currentSettingsWithoutAdditionalParsing['BE']['installToolPassword'] ?? '') !== ($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'] ?? '')) { + $io->error('Your Install Tool password is different in settings.php and additional.php. This command can only effectively change the password for "settings.php" and therefore any changes would not take effect.'); + return Command::FAILURE; + } + + $dryRun = $input->getOption('dry-run'); + $noInteraction = $input->getOption('no-interaction'); + + $password = !$noInteraction + ? $this + ->getQuestionHelper() + ->ask($input, $output, (new Question('Password (leave empty for auto generation): '))->setHidden(true)) + : null; + + if ($password === null) { + $generator = $GLOBALS['TYPO3_CONF_VARS']['SYS']['passwordPolicies']['installTool']['generator'] ?? null; + if (!class_exists($generator['className'] ?? '') || !is_array($generator['options'] ?? null)) { + throw new \LogicException( + 'The TYPO3_CONF_VARS.SYS.passwordPolicies.installTool.generator configuration is misconfigured.' + . ' Please ensure that the sub key \'className\' is set, and the sub key \'options\' is an array of required option values.', + 1770131006 + ); + } + + $passwordGeneratorClassName = $generator['className']; + $passwordGeneratorOptions = $generator['options']; + + $passwordGenerator = GeneralUtility::makeInstance($passwordGeneratorClassName); + if (!$passwordGenerator instanceof PasswordGeneratorInterface) { + throw new \LogicException('Class ' . $passwordGeneratorClassName . ' does not implement PasswordGeneratorInterface', 1770131293); + } + + if ($input->getOption('password-length') !== null) { + $passwordGeneratorOptions['length'] = (int)$input->getOption('password-length'); + } + $length = $passwordGeneratorOptions['length']; + $password = $passwordGenerator->generate($passwordGeneratorOptions); + $output->writeln(sprintf('Password length: %d characters', $length)); + // A generated password may contain '<', '>' or '\', which Symfony's + // OutputFormatter would silently mangle on display. Emit the password via + // OUTPUT_RAW so it bypasses the formatter entirely, and apply the 'info' + // style manually when decoration is on to keep the green highlight. + $coloredPassword = $output->isDecorated() + ? $output->getFormatter()->getStyle('info')->apply($password) + : $password; + $output->write('Generated password: '); + $output->writeln($coloredPassword, OutputInterface::OUTPUT_RAW); + } + + // Validation error messages require a valid LANG object to operate on (or a backend user context, which we don't need here) + $GLOBALS['LANG'] = $this->languageServiceFactory->create('en'); + $validationResultErrors = $this->passwordService->getValidationErrorsForInstallToolUpdate($password); + if ($validationResultErrors !== []) { + $output->writeln('Your password could not be used. The following validation rules did not pass:'); + foreach ($validationResultErrors as $validatorKey => $message) { + $output->writeln(sprintf(' - %s (%s)', $message, $validatorKey)); + } + return Command::FAILURE; + } + + $passwordHashed = $this->passwordHashFactory->getDefaultHashInstance('BE')->getHashedPassword($password); + + if ($dryRun) { + $output->writeln(sprintf('Password hashed (dry run): %s', $passwordHashed)); + } else { + $this + ->configurationManager + ->setLocalConfigurationValueByPath('BE/installToolPassword', $passwordHashed); + + $output->writeln('Install Tool password updated in "settings.php".'); + $output->writeln('Please note that a custom override of this password in "additional.php" will have higher priority.'); + } + + return Command::SUCCESS; + } + + private function getQuestionHelper(): QuestionHelper + { + /** @var QuestionHelper $helper */ + $helper = $this->getHelper('question'); + return $helper; + } +} diff --git a/Classes/Command/SetupCommand.php b/Classes/Command/SetupCommand.php new file mode 100644 index 0000000..e8e7671 --- /dev/null +++ b/Classes/Command/SetupCommand.php @@ -0,0 +1,774 @@ + '[MySQLi] Manually configured MySQL TCP/IP connection', + 'mysqliSocket' => '[MySQLi] Manually configured MySQL socket connection', + 'pdoMysql' => '[PDO] Manually configured MySQL TCP/IP connection', + 'pdoMysqlSocket' => '[PDO] Manually configured MySQL socket connection', + 'postgres' => 'Manually configured PostgreSQL connection', + 'sqlite' => 'Manually configured SQLite connection', + ]; + + public function __construct( + string $name, + private readonly SetupService $setupService, + private readonly ConfigurationManager $configurationManager, + private readonly LateBootService $lateBootService, + private readonly FailsafePackageManager $packageManager, + ) { + parent::__construct($name); + } + + protected function configure(): void + { + $this->setDescription('Setup TYPO3 via CLI using environment variables, CLI options or interactive') + // Connection Parameters + ->addOption( + 'driver', + null, + InputOption::VALUE_OPTIONAL, + 'Select which database driver to use', + ) + ->addOption( + 'host', + null, + InputOption::VALUE_REQUIRED, + 'Set the database host to use', + 'db' + ) + ->addOption( + 'port', + null, + InputOption::VALUE_OPTIONAL, + 'Set the database port to use', + '3306' + ) + ->addOption( + 'dbname', + null, + InputOption::VALUE_REQUIRED, + 'Set the database name to use', + 'db' + ) + ->addOption( + 'username', + null, + InputOption::VALUE_REQUIRED, + 'Set the database username to use', + 'db' + ) + ->addOption( + 'password', + null, + InputOption::VALUE_REQUIRED, + 'Set the database password to use' + ) + // User to be created + ->addOption( + 'admin-username', + null, + InputOption::VALUE_OPTIONAL, + 'Set a username', + 'admin' + ) + ->addOption( + 'admin-user-password', + null, + InputOption::VALUE_REQUIRED, + 'Set users password' + ) + ->addOption( + 'admin-email', + null, + InputOption::VALUE_REQUIRED, + 'Set users email', + '' + ) + ->addOption( + 'project-name', + null, + InputOption::VALUE_REQUIRED, + 'Set the TYPO3 project name', + 'New TYPO3 Project' + ) + ->addOption( + 'create-site', + null, + InputOption::VALUE_REQUIRED, + 'Create a basic site setup (root page and site configuration) with the given domain, ex. "https://my.domain.tld/"', + false + ) + ->addOption( + 'distribution', + null, + InputOption::VALUE_OPTIONAL, + $this->packageManager->isPackageActive('impexp') + ? 'Import a distribution during site creation (package key, e.g. "theme_camino")' + : '[disabled] Requires typo3/cms-impexp to be installed' + ) + ->addOption( + 'server-type', + null, + InputOption::VALUE_OPTIONAL, + 'Define the web server the TYPO3 installation will be running on', + 'other' + ) + ->addOption( + 'force', + null, + InputOption::VALUE_NONE, + 'Force settings overwrite - use this if TYPO3 has been installed already', + ) + ->addOption( + 'no-interaction', + 'n', + InputOption::VALUE_NONE, + 'Do not ask any interactive question', + )->setHelp( + <<Setup using environment variables +--------------------------------- +TYPO3_DB_DRIVER=mysqli \ +TYPO3_DB_USERNAME=db \ +TYPO3_DB_PORT=3306 \ +TYPO3_DB_HOST=db \ +TYPO3_DB_DBNAME=db \ +TYPO3_SETUP_ADMIN_EMAIL=admin@example.com \ +TYPO3_SETUP_ADMIN_USERNAME=admin \ +TYPO3_SETUP_CREATE_SITE="https://your-typo3-site.com/" \ +TYPO3_SETUP_DISTRIBUTION="theme_camino" \ +TYPO3_PROJECT_NAME="Automated Setup" \ +TYPO3_SERVER_TYPE="apache" \ +./bin/typo3 setup --force +--------------------------------- + + +Variable `TYPO3_DB_PASSWORD` (option `--password`) can be used to provide a +password for the database and `TYPO3_SETUP_ADMIN_PASSWORD` +(option `--admin-user-password`) for the admin user password. +Using this can be a security risk since the password may end up in shell +history files. Prefer the interactive mode. Additionally, writing a command +to shell history can be suppressed by prefixing the command with a space +when using `bash` or `zsh`. + +EOT + ); + } + + /** + * Runs the installation / setup process + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $input->setInteractive(!$input->getOption('no-interaction')); + /** @var QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + // Ensure all required files and folders exist + $this->setupService->createDirectoryStructure($this->getServerType($questionHelper, $input, $output)); + + try { + $force = $input->getOption('force'); + $this->setupService->prepareSystemSettings($force); + } catch (ConfigurationFileAlreadyExistsException) { + $configOverwriteQuestion = new ConfirmationQuestion( + 'Configuration already exists do you want to overwrite it [default: no] ? ', + false + ); + $configOverwrite = $questionHelper->ask($input, $output, $configOverwriteQuestion); + + if (!$configOverwrite) { + return Command::FAILURE; + } + + $this->setupService->prepareSystemSettings(true); + } + + $container = $this->lateBootService->getContainer(); + $backup = $this->lateBootService->makeCurrent($container); + $setupDatabaseService = $container->get(SetupDatabaseService::class); + + // Get database connection details + $databaseConnection = $this->getConnectionDetails($setupDatabaseService, $questionHelper, $input, $output); + + // Select the database and prepare it + if ($exitCode = $this->selectAndImportDatabase($setupDatabaseService, $questionHelper, $input, $output, $databaseConnection)) { + return $exitCode; + } + + $container->get(CommandLineUserCreation::class)->ensureCliUserExists(); + $this->lateBootService->makeCurrent(null, $backup); + + $username = $this->getAdminUserName($questionHelper, $input, $output); + $password = $this->getAdminUserPassword($setupDatabaseService, $questionHelper, $input, $output); + if ($password !== null) { + $email = $this->getAdminEmailAddress($questionHelper, $input, $output); + $this->setupService->createUser($username, $password, $email); + $this->setupService->setInstallToolPassword($password); + } elseif ($output->isVerbose()) { + $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + $errOutput->writeln('No admin password defined. Skipped user creation.'); + } + + $siteName = $this->getProjectName($questionHelper, $input, $output); + $this->setupService->setSiteName($siteName); + + $distributions = $this->setupService->getAvailableDistributions(); + $distributionFromCli = $this->getFallbackValueEnvOrOption($input, 'distribution', 'TYPO3_SETUP_DISTRIBUTION'); + $createSiteFromCli = $this->getFallbackValueEnvOrOption($input, 'create-site', 'TYPO3_SETUP_CREATE_SITE'); + if ($distributionFromCli !== false && $createSiteFromCli !== false) { + throw new \RuntimeException( + 'The --distribution and --create-site commandline options may not be used at the same time', + 1775034289 + ); + } + if ($distributions['active'] === []) { + $selectedDistribution = $this->getDistributionForActivation($distributions['inactive'], $questionHelper, $input, $output); + if ($selectedDistribution !== null) { + // Distribution handles all site creation (pages, content, site configuration) + $this->setupService->activateDistributionPackage($selectedDistribution); + } else { + $siteUrl = $this->getSiteSetup($questionHelper, $input, $output); + if ($siteUrl) { + $this->setupService->createSite('main', $siteUrl); + } + } + } elseif ($distributionFromCli !== false || $createSiteFromCli !== false) { + $this->writeWarning( + $output, + 'The --distribution and --create-site commandline options have no effect, when distributions are already active' + ); + } + // The new container is kept in GeneralUtility because the following code, especially + // the current state of the data import during extension setup still relies on GeneralUtility::makeInstance + // to fetch objects from the container + $container = $this->lateBootService->loadExtLocalconfDatabase(false); + $setupDatabaseService->markWizardsDone($container); + Bootstrap::initializeBackendAuthentication(); + $this->setupService->setupExtensions($container); + $this->writeSuccess($output, 'Congratulations - TYPO3 Setup is done.'); + + return Command::SUCCESS; + } + + protected function selectAndImportDatabase( + SetupDatabaseService $setupDatabaseService, + QuestionHelper $questionHelper, + InputInterface $input, + OutputInterface $output, + mixed $databaseConnection, + ): int { + if ($databaseConnection['driver'] !== 'pdo_sqlite') { + // Set temporary database configuration, so we are able to + // get the available databases listed + $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME] = $databaseConnection; + + try { + $databaseList = $setupDatabaseService->getDatabaseList(); + } catch (DBALException $exception) { + $this->writeError($output, $exception->getMessage()); + + return Command::FAILURE; + } + + if ($databaseList === []) { + $this->writeError($output, 'No databases are available to the specified user. At least one usable database needs to be accessible.'); + return Command::FAILURE; + } + + $dbChoices = []; + foreach ($databaseList as $database) { + $usable = $database['tables'] > 0 ? '☓' : '✓'; + $dbChoices[$database['name']] = $database['name'] . ' (Tables ' . $database['tables'] . ' ' . $usable . ')'; + } + + $dbNameValidator = static function ($dbname) use ($dbChoices, $databaseList) { + if (!($dbChoices[$dbname] ?? false)) { + throw new \RuntimeException( + 'The selected database "' . $dbname . '" is not available, pick one of these: ' . implode( + ', ', + array_keys($dbChoices) + ) . '.', + 1669747192, + ); + } + + $selectedDatabase = $databaseList[array_search($dbname, array_keys($dbChoices), true)]; + if ($selectedDatabase['tables'] !== 0) { + throw new \RuntimeException( + 'The selected database contains already ' . $selectedDatabase['tables'] . ' tables. Please delete all tables or select another database.', + 1669747200, + ); + } + + return $dbname; + }; + + $dbnameFromCli = $this->getFallbackValueEnvOrOption($input, 'dbname', 'TYPO3_DB_DBNAME'); + if ($dbnameFromCli === false && $input->isInteractive()) { + $dbname = new ChoiceQuestion('Select which database to use: ', $dbChoices); + $dbname->setValidator($dbNameValidator); + $databaseConnection['database'] = $questionHelper->ask($input, $output, $dbname); + } else { + try { + $dbNameValidator($dbnameFromCli); + } catch (\RuntimeException $e) { + $this->writeError($output, $e->getMessage()); + + return Command::FAILURE; + } + + $databaseConnection['database'] = $dbnameFromCli; + } + + $checkDatabase = $setupDatabaseService->checkExistingDatabase($databaseConnection['database']); + if ($checkDatabase->getSeverity() !== ContextualFeedbackSeverity::OK) { + $this->writeError($output, $checkDatabase->getMessage()); + + return Command::FAILURE; + } + } else { + $databaseConnection['availableSet'] = 'sqliteManualConfiguration'; + } + + [$success, $messages] = $setupDatabaseService->setDefaultConnectionSettings($databaseConnection); + if (!$success) { + foreach ($messages as $message) { + $this->writeError($output, $message->getMessage()); + } + + return Command::FAILURE; + } + + // Load the actual config written to disk + $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME] = $this->configurationManager->getLocalConfigurationValueByPath('DB/Connections/Default'); + + $setupDatabaseService->checkRequiredDatabasePermissions(); + $importResults = $setupDatabaseService->importDatabaseData(); + foreach ($importResults as $result) { + $this->writeError($output, (string)$result); + } + + if (count($importResults) > 0) { + $this->writeError($output, 'Database import failed. Please see the errors shown above'); + return Command::FAILURE; + } + + return Command::SUCCESS; + } + + protected function getConnectionDetails( + SetupDatabaseService $setupDatabaseService, + QuestionHelper $questionHelper, + InputInterface $input, + OutputInterface $output, + ): array { + $input->hasParameterOption('--driver'); + $driverTypeCli = $this->getFallbackValueEnvOrOption($input, 'driver', 'TYPO3_DB_DRIVER'); + $driverOptions = $setupDatabaseService->getDriverOptions(); + $availableConnectionTypes = implode(', ', array_keys($this->connectionLabels)); + + $connectionValidator = static function ($connectionType) use ($driverOptions, $availableConnectionTypes) { + if (!isset($driverOptions[$connectionType . 'ManualConfigurationOptions'])) { + throw new \RuntimeException( + 'The connection type "' . $connectionType . '" does not exist. Please use one of the following ' . $availableConnectionTypes, + 1669905551, + ); + } + + return $connectionType; + }; + + if ($driverTypeCli === false && $input->isInteractive()) { + $driver = new ChoiceQuestion('Database driver?', $this->connectionLabels); + $driver->setValidator($connectionValidator); + $driverType = $questionHelper->ask($input, $output, $driver); + } else { + $driverType = $connectionValidator($driverTypeCli); + } + + $databaseConnectionOptions = $driverOptions[$driverType . 'ManualConfigurationOptions']; + + // Ask for connection details + foreach ($databaseConnectionOptions as $key => $value) { + switch ($key) { + case 'database': + case 'socket': + case 'driver': + break; + case 'username': + $usernameFromCli = $this->getFallbackValueEnvOrOption($input, 'username', 'TYPO3_DB_USERNAME'); + $emptyValidator = static function ($value) { + if (empty($value)) { + throw new \RuntimeException( + 'The value must not be empty.', + 1669747578, + ); + } + + return $value; + }; + + if ($usernameFromCli === false && $input->isInteractive()) { + $default = $this->getDefinition()->getOption($key)->getDefault(); + $defaultLabel = ' [default: ' . $default . ']'; + $question = new Question('Enter the database "username"' . $defaultLabel . ' ? ', $default); + $question->setValidator($emptyValidator); + $username = $questionHelper->ask($input, $output, $question); + // @todo: Investigate the difference between username and user.... why? + $databaseConnectionOptions['username'] = $username; + $databaseConnectionOptions['user'] = $username; + break; + } + + $validUsername = $emptyValidator($usernameFromCli); + $databaseConnectionOptions['username'] = $validUsername; + $databaseConnectionOptions['user'] = $validUsername; + break; + default: + $envValue = $this->getFallbackValueEnvOrOption($input, $key, 'TYPO3_DB_' . strtoupper($key)); + $default = $this->getDefinition()->getOption($key)->getDefault(); + $defaultLabel = empty($value) ? '' : ' [default: ' . $default . ']'; + $question = new Question('Enter the database "' . $key . '"' . $defaultLabel . ' ? ', $default); + if ($key === 'password') { + $question = new Question('Enter the database "' . $key . '" ? ', $default); + $question->setHidden(true); + $question->setHiddenFallback(false); + } elseif ($key === 'host') { + $hostValidator = function ($host) use ($setupDatabaseService) { + if (!$setupDatabaseService->isValidDbHost($host)) { + throw new \RuntimeException( + 'Please enter a valid database host name.', + 1669747572 + ); + } + return $host; + }; + $question->setValidator($hostValidator); + } elseif ($key === 'port') { + $portValidator = function ($port) use ($setupDatabaseService) { + $port = (int)$port; + if (!$setupDatabaseService->isValidDbPort($port)) { + throw new \RuntimeException( + 'Please use a port in the range between 1 and 65535.', + 1669747592, + ); + } + return $port; + }; + $question->setValidator($portValidator); + } else { + $emptyValidator = function ($value) { + if (empty($value)) { + throw new \RuntimeException( + 'The value must not be empty.', + 1669747601, + ); + } + return $value; + }; + $question->setValidator($emptyValidator); + } + if ($envValue === false && $key === 'password') { + // Force this question if no `TYPO3_DB_PASSWORD` set via cli. + // Thus, the user will always be prompted for a password even --no-interaction is set. + $currentlyInteractive = $input->isInteractive(); + $input->setInteractive(true); + $value = $questionHelper->ask($input, $output, $question); + $input->setInteractive($currentlyInteractive); + } elseif ($envValue === false && $input->isInteractive()) { + $value = $questionHelper->ask($input, $output, $question); + } else { + // All passed in values should go through the set validator, + // therefore, we can't break early + $validator = $question->getValidator(); + $envValue = $envValue ?: $default; + $value = $validator ? $validator($envValue) : $envValue; + } + $databaseConnectionOptions[$key] = $value; + } + } + + return $databaseConnectionOptions; + } + + protected function getServerType(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output): WebserverType + { + $serverTypeValidator = static function (?string $serverType): WebserverType { + if (!array_key_exists($serverType, WebserverType::getDescriptions())) { + throw new \RuntimeException( + 'Webserver must be any of ' . implode(', ', array_keys(WebserverType::getDescriptions())), + 1682329380, + ); + } + + return WebserverType::from($serverType); + }; + $serverTypeFromCli = $this->getFallbackValueEnvOrOption($input, 'server-type', 'TYPO3_SERVER_TYPE'); + if ($serverTypeFromCli === false && $input->isInteractive()) { + $questionServerType = new ChoiceQuestion('Which web server is used?', WebserverType::getDescriptions()); + $questionServerType->setValidator($serverTypeValidator); + return $questionHelper->ask($input, $output, $questionServerType); + } + + return $serverTypeValidator($serverTypeFromCli); + } + + protected function getAdminUserName(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output): string + { + $usernameValidator = static function ($username) { + if (empty($username)) { + throw new \RuntimeException( + 'Admin username must not be empty.', + 1669747607, + ); + } + + return $username; + }; + + $usernameFromCli = $this->getFallbackValueEnvOrOption($input, 'admin-username', 'TYPO3_SETUP_ADMIN_USERNAME'); + if ($usernameFromCli === false && $input->isInteractive()) { + $questionUsername = new Question('Admin username (user will be "system maintainer") ? '); + $questionUsername->setValidator($usernameValidator); + return $questionHelper->ask($input, $output, $questionUsername); + } + + // Use default value for 'admin-username' if in non-interactive mode + if ($usernameFromCli === false && !$input->isInteractive()) { + $usernameFromCli = $this->getDefinition()->getOption('admin-username')->getDefault(); + } + + return $usernameValidator($usernameFromCli); + } + + protected function getAdminUserPassword( + SetupDatabaseService $setupDatabaseService, + QuestionHelper $questionHelper, + InputInterface $input, + OutputInterface $output, + ): ?string { + $passwordValidator = function ($password) use ($setupDatabaseService) { + $passwordValidationErrors = $setupDatabaseService->getBackendUserPasswordValidationErrors((string)$password); + if (!empty($passwordValidationErrors)) { + throw new \RuntimeException( + 'Administrator password not secure enough!' . PHP_EOL + . '* ' . implode(PHP_EOL . '* ', $passwordValidationErrors), + 1669747614, + ); + } + + return $password; + }; + + $passwordFromCli = $this->getFallbackValueEnvOrOption($input, 'admin-user-password', 'TYPO3_SETUP_ADMIN_PASSWORD'); + if ($passwordFromCli === false && $input->isInteractive()) { + $currentlyInteractive = $input->isInteractive(); + $input->setInteractive(true); + $questionPassword = new Question('Admin user and installer password ? '); + $questionPassword->setHidden(true); + $questionPassword->setHiddenFallback(false); + $questionPassword->setValidator($passwordValidator); + $password = $questionHelper->ask($input, $output, $questionPassword); + $input->setInteractive($currentlyInteractive); + + return $password; + } + + if ($passwordFromCli === false) { + return null; + } + + return $passwordValidator($passwordFromCli); + } + + protected function getAdminEmailAddress(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output): string + { + $emailValidator = static function ($email) { + if (!empty($email) && !GeneralUtility::validEmail($email)) { + throw new \RuntimeException( + 'The given Email is not valid! Please try again.', + 1669747620, + ); + } + + return $email; + }; + + $emailFromCli = $this->getFallbackValueEnvOrOption($input, 'admin-email', 'TYPO3_SETUP_ADMIN_EMAIL'); + if ($emailFromCli === false && $input->isInteractive()) { + $questionEmail = new Question('Admin user email ? ', ''); + $questionEmail->setValidator($emailValidator); + + return $questionHelper->ask($input, $output, $questionEmail); + } + + return (string)$emailValidator($emailFromCli); + } + + protected function getProjectName(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output): string + { + $nameFromCli = $this->getFallbackValueEnvOrOption($input, 'project-name', 'TYPO3_PROJECT_NAME'); + $defaultProjectName = $this->getDefinition()->getOption('project-name')->getDefault(); + + if ($nameFromCli === false && $input->isInteractive()) { + $question = new Question( + 'Give your project a name [default: ' . $defaultProjectName . '] ? ', + $defaultProjectName + ); + + return $questionHelper->ask($input, $output, $question); + } + + return $nameFromCli ?: $defaultProjectName; + } + + protected function getSiteSetup(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output): string|bool + { + $urlValidator = static function ($url) { + if (!is_string($url) || in_array(strtolower($url), ['no', 'n'], true)) { + return false; + } + if (empty($url) || !GeneralUtility::isValidUrl($url)) { + throw new \RuntimeException( + 'Invalid URL provided for the site name. Please provide a valid URL.', + 1669747625, + ); + } + + return $url; + }; + + $createSiteFromCli = $this->getFallbackValueEnvOrOption($input, 'create-site', 'TYPO3_SETUP_CREATE_SITE'); + + if ($createSiteFromCli === false && $input->isInteractive()) { + $questionCreateSite = new Question('Create a basic site? Please enter a URL [default: no] ', false); + $questionCreateSite->setValidator($urlValidator); + + return $questionHelper->ask($input, $output, $questionCreateSite); + } + + return $urlValidator($createSiteFromCli); + } + + protected function getDistributionForActivation(array $inactiveDistributions, QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output): ?string + { + $distributionFromCli = $this->getFallbackValueEnvOrOption($input, 'distribution', 'TYPO3_SETUP_DISTRIBUTION'); + + if (!$this->packageManager->isPackageActive('impexp')) { + if ($distributionFromCli !== false) { + throw new \RuntimeException( + sprintf('Distribution "%s" is not installable, please require typo3/cms-impexp.', $distributionFromCli), + 1775034287 + ); + } + return null; + } + + if ($distributionFromCli !== false) { + if (!isset($inactiveDistributions[$distributionFromCli])) { + throw new \RuntimeException( + sprintf('Distribution "%s" is not available.', $distributionFromCli), + 1775034288 + ); + } + return $distributionFromCli; + } + + if ($input->isInteractive()) { + $choices = ['none' => 'Do not import a distribution']; + foreach ($inactiveDistributions as $packageKey => $info) { + $choices[$packageKey] = $info['title']; + } + $question = new ChoiceQuestion('Select a distribution to import [default: none]', $choices, 'none'); + $answer = $questionHelper->ask($input, $output, $question); + return $answer === 'none' ? null : $answer; + } + + return null; + } + + protected function writeSuccess(OutputInterface $output, string $message): void + { + $output->writeln('✓ ' . $message); + } + + protected function writeWarning(OutputInterface $output, string $message): void + { + $output->writeln('! [Warning]: ' . $message); + } + + protected function writeError(OutputInterface $output, string $message): void + { + $output->writeln('☓ [Error]: ' . $message); + } + + /** + * Get a value from + * + * 1. cli option `$option` + * 2. environment variable `$envVar` + * + * Note that cli option has higher precedences and wins over environment variable. + */ + protected function getFallbackValueEnvOrOption(InputInterface $input, string $option, string $envVar): string|false + { + $value = ($input->hasParameterOption('--' . $option)) + ? $input->getOption($option) + : getenv($envVar); + return is_string($value) ? $value : false; + } +} diff --git a/Classes/Command/SetupDefaultBackendUserGroupsCommand.php b/Classes/Command/SetupDefaultBackendUserGroupsCommand.php new file mode 100644 index 0000000..f026068 --- /dev/null +++ b/Classes/Command/SetupDefaultBackendUserGroupsCommand.php @@ -0,0 +1,125 @@ +availableUserGroups = $this->userGroupEnum->getAllUserGroupTypes(); + $actualGroups = $this->userGroupEnum->getActualUserGroupTypes(); + $stringUserGroupList = $actualGroups !== [] + ? '- ' . implode("\n- ", $actualGroups) + : 'No groups available.'; + $this->setDescription('Setup default backend user groups') + ->addOption( + 'no-interaction', + 'n', + InputOption::VALUE_NONE, + 'Do not ask any interactive question', + )->addOption( + 'groups', + 'g', + InputOption::VALUE_OPTIONAL, + 'Which backend user groups do you want to create? [ ' . implode(', ', $this->availableUserGroups) . ']', + $this->userGroupEnum->value, + $this->availableUserGroups + )->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force creating a new group with the same name, even if a group with that name already exists.' + )->setHelp( + <<setInteractive(!$input->getOption('no-interaction')); + $io = new SymfonyStyle($input, $output); + + $createGroups = $this->userGroupEnum->value; + if ($input->hasParameterOption('--groups') || $input->hasParameterOption('-g')) { + $createGroups = $input->getOption('groups'); + } elseif (!$input->getOption('no-interaction')) { + $createGroups = $io->choice( + 'Which backend groups do you want to create?', + $this->availableUserGroups, + $this->userGroupEnum->value + ); + } + if ($createGroups === BackendUserGroupType::NONE->value) { + $io->info('No backend groups have been created.'); + return Command::SUCCESS; + } + if (!in_array($createGroups, $this->availableUserGroups, true)) { + $io->error('Invalid user group specified.'); + return Command::FAILURE; + } + + $createEditor = false; + $createAdvancedEditor = false; + $creationNotices = []; + if ($createGroups === BackendUserGroupType::EDITOR->value || $createGroups === BackendUserGroupType::ALL->value) { + $createEditor = true; + $creationNotices[] = BackendUserGroupType::EDITOR->value; + } + if ($createGroups === BackendUserGroupType::ADVANCED_EDITOR->value || $createGroups === BackendUserGroupType::ALL->value) { + $createAdvancedEditor = true; + $creationNotices[] = BackendUserGroupType::ADVANCED_EDITOR->value; + } + $messages = $this->setupService->createBackendUserGroups($createEditor, $createAdvancedEditor, $input->hasParameterOption('-f')); + + if ($messages !== []) { + foreach ($messages as $message) { + $io->warning($message); + } + return Command::FAILURE; + } + $io->success(sprintf('Backend user group(s) created: %s', implode(', ', $creationNotices))); + return Command::SUCCESS; + } +} diff --git a/Classes/Configuration/AbstractCustomPreset.php b/Classes/Configuration/AbstractCustomPreset.php new file mode 100644 index 0000000..dbdcf06 --- /dev/null +++ b/Classes/Configuration/AbstractCustomPreset.php @@ -0,0 +1,110 @@ +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 to persist data and is merged with given $postValues. + * + * @return array Configuration values needed to activate prefix + */ + public function getConfigurationValues() + { + return array_map(static fn($configuration) => $configuration['value'], $this->getConfigurationDescriptors()); + } + + /** + * Build configuration descriptors to be 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 getConfigurationDescriptors() + { + $configurationValues = []; + foreach ($this->configurationValues as $configurationKey => $configurationValue) { + $readonly = isset($this->readonlyConfigurationValues[$configurationKey]); + if (!$readonly + && 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] = [ + 'value' => $currentValue, + 'readonly' => $readonly, + ]; + } + return $configurationValues; + } +} diff --git a/Classes/Configuration/AbstractFeature.php b/Classes/Configuration/AbstractFeature.php new file mode 100644 index 0000000..51076ae --- /dev/null +++ b/Classes/Configuration/AbstractFeature.php @@ -0,0 +1,131 @@ +name])) { + $featurePostValues = $postValues[$this->name]; + } + $this->postValues = $featurePostValues; + + $isNonCustomPresetActive = false; + $customPresetFound = false; + foreach ($this->presetRegistry as $presetClass) { + $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; + } +} diff --git a/Classes/Configuration/AbstractPreset.php b/Classes/Configuration/AbstractPreset.php new file mode 100644 index 0000000..fb45c4b --- /dev/null +++ b/Classes/Configuration/AbstractPreset.php @@ -0,0 +1,152 @@ +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 === null && $configurationValue === '__UNSET') { + // Preset can define configuration values to be removed using `__UNSET` value handled by + // `ArrayUtility::mergeRecursiveWithOverrule()`, retrieving `$currentValue = null`. This + // case needs to be skipped to avoid taking the unset value of the other preset to flag + // the current preset as inactive. Continue with next value. + continue; + } + 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; + } +} diff --git a/Classes/Configuration/Cache/CacheFeature.php b/Classes/Configuration/Cache/CacheFeature.php new file mode 100644 index 0000000..86eceea --- /dev/null +++ b/Classes/Configuration/Cache/CacheFeature.php @@ -0,0 +1,40 @@ + Typo3DatabaseBackend::class, + 'SYS/caching/cacheConfigurations/pages/backend' => Typo3DatabaseBackend::class, + 'SYS/caching/cacheConfigurations/rootline/backend' => Typo3DatabaseBackend::class, + ]; +} diff --git a/Classes/Configuration/Cache/DatabaseCachePreset.php b/Classes/Configuration/Cache/DatabaseCachePreset.php new file mode 100644 index 0000000..940dd4b --- /dev/null +++ b/Classes/Configuration/Cache/DatabaseCachePreset.php @@ -0,0 +1,53 @@ + Typo3DatabaseBackend::class, + 'SYS/caching/cacheConfigurations/pages/backend' => Typo3DatabaseBackend::class, + 'SYS/caching/cacheConfigurations/pages/options/compression' => true, + 'SYS/caching/cacheConfigurations/rootline/backend' => Typo3DatabaseBackend::class, + 'SYS/caching/cacheConfigurations/rootline/options/compression' => true, + ]; + + /** + * Database is always enabled + * + * @return bool TRUE if sendmail path if set + */ + public function isAvailable() + { + return true; + } +} diff --git a/Classes/Configuration/Cache/FileCachePreset.php b/Classes/Configuration/Cache/FileCachePreset.php new file mode 100644 index 0000000..146ff56 --- /dev/null +++ b/Classes/Configuration/Cache/FileCachePreset.php @@ -0,0 +1,53 @@ + FileBackend::class, + 'SYS/caching/cacheConfigurations/pages/backend' => FileBackend::class, + 'SYS/caching/cacheConfigurations/pages/options/compression' => '__UNSET', + 'SYS/caching/cacheConfigurations/rootline/backend' => FileBackend::class, + 'SYS/caching/cacheConfigurations/rootline/options/compression' => '__UNSET', + ]; + + /** + * Database is always enabled + * + * @return bool TRUE if sendmail path if set + */ + public function isAvailable() + { + return true; + } +} diff --git a/Classes/Configuration/Context/ContextFeature.php b/Classes/Configuration/Context/ContextFeature.php new file mode 100644 index 0000000..256a718 --- /dev/null +++ b/Classes/Configuration/Context/ContextFeature.php @@ -0,0 +1,40 @@ + '', + 'FE/debug' => '', + 'SYS/devIPmask' => '', + 'SYS/displayErrors' => '', + ]; +} diff --git a/Classes/Configuration/Context/DebugPreset.php b/Classes/Configuration/Context/DebugPreset.php new file mode 100644 index 0000000..4a6913f --- /dev/null +++ b/Classes/Configuration/Context/DebugPreset.php @@ -0,0 +1,77 @@ + true, + 'FE/debug' => true, + 'SYS/devIPmask' => '*', + 'SYS/displayErrors' => 1, + // 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() + { + $priority = $this->priority; + if (Environment::getContext()->isDevelopment()) { + $priority = $priority + 20; + } + return $priority; + } +} diff --git a/Classes/Configuration/Context/LivePreset.php b/Classes/Configuration/Context/LivePreset.php new file mode 100644 index 0000000..dfe646c --- /dev/null +++ b/Classes/Configuration/Context/LivePreset.php @@ -0,0 +1,77 @@ + false, + 'FE/debug' => false, + 'SYS/devIPmask' => '', + 'SYS/displayErrors' => 0, + // 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() + { + $priority = $this->priority; + if (Environment::getContext()->isProduction()) { + $priority = $priority + 20; + } + return $priority; + } +} diff --git a/Classes/Configuration/CustomPresetInterface.php b/Classes/Configuration/CustomPresetInterface.php new file mode 100644 index 0000000..9e4e082 --- /dev/null +++ b/Classes/Configuration/CustomPresetInterface.php @@ -0,0 +1,37 @@ + + */ + public function getPresetsOrderedByPriority(); + + /** + * Get name of feature + * + * @return string Name + */ + public function getName(); +} diff --git a/Classes/Configuration/FeatureManager.php b/Classes/Configuration/FeatureManager.php new file mode 100644 index 0000000..d854ddf --- /dev/null +++ b/Classes/Configuration/FeatureManager.php @@ -0,0 +1,126 @@ +featureRegistry as $featureClass) { + $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) { + $featureName = $feature->getName(); + $presets = $feature->getPresetsOrderedByPriority(); + foreach ($presets as $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) { + $presets = $feature->getPresetsOrderedByPriority(); + foreach ($presets as $preset) { + // Only choose "normal" presets, no custom presets + if ($preset instanceof CustomPresetInterface) { + break; + } + + if ($preset->isAvailable()) { + $localConfigurationValuesToSet = array_merge( + $localConfigurationValuesToSet, + $preset->getConfigurationValues() + ); + // Setting for this feature done, go to next feature + break; + } + } + } + return $localConfigurationValuesToSet; + } +} diff --git a/Classes/Configuration/Image/AbstractImagePreset.php b/Classes/Configuration/Image/AbstractImagePreset.php new file mode 100644 index 0000000..aa1d84e --- /dev/null +++ b/Classes/Configuration/Image/AbstractImagePreset.php @@ -0,0 +1,152 @@ +foundPath; + } + + /** + * Check is preset is currently active on the system. + * Overwrites parent method to ignore processor_path setting + * + * @return bool TRUE if preset is active + */ + public function isActive() + { + $isActive = true; + foreach ($this->configurationValues as $configurationKey => $configurationValue) { + if ($configurationKey !== 'GFX/processor_path') { + $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(); + return $configurationValues; + } + + /** + * Find executable in path, wrapper for specific ImageMagick/GraphicsMagick find methods. + * + * @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 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 = GeneralUtility::fixWindowsFilePath((string)$path); + // Add trailing slash if missing + if (!preg_match('/[\\/]$/', $path)) { + $path .= '/'; + } + return $path; + } +} diff --git a/Classes/Configuration/Image/CustomPreset.php b/Classes/Configuration/Image/CustomPreset.php new file mode 100644 index 0000000..cfdefc0 --- /dev/null +++ b/Classes/Configuration/Image/CustomPreset.php @@ -0,0 +1,41 @@ + false, + 'GFX/processor_path' => '', + 'GFX/processor' => '', + 'GFX/processor_effects' => false, + 'GFX/processor_colorspace' => '', + ]; + + protected $readonlyConfigurationValues = [ + 'GFX/processor_path' => true, + ]; +} diff --git a/Classes/Configuration/Image/GraphicsMagickPreset.php b/Classes/Configuration/Image/GraphicsMagickPreset.php new file mode 100644 index 0000000..615fbc2 --- /dev/null +++ b/Classes/Configuration/Image/GraphicsMagickPreset.php @@ -0,0 +1,93 @@ + true, + // processor_path is determined and set by path lookup methods + 'GFX/processor_path' => '', + 'GFX/processor' => 'GraphicsMagick', + 'GFX/processor_effects' => false, + ]; + + /** + * Find executable in path, wrapper for specific ImageMagick/GraphicsMagick find methods. + * + * @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'; + } + + $binaryPath = $path . $executable; + if (!file_exists($binaryPath) || !is_executable($binaryPath)) { + continue; + } + + $command = escapeshellarg($binaryPath) . ' -version'; + $executingResult = []; + @CommandUtility::exec($command, $executingResult); + // First line of exec command should contain string GraphicsMagick + $firstResultLine = array_shift($executingResult); + if (is_string($firstResultLine) && str_contains($firstResultLine, 'GraphicsMagick')) { + $this->foundPath = $path; + $result = true; + break; + } + } + + return $result; + } +} diff --git a/Classes/Configuration/Image/ImageFeature.php b/Classes/Configuration/Image/ImageFeature.php new file mode 100644 index 0000000..539c081 --- /dev/null +++ b/Classes/Configuration/Image/ImageFeature.php @@ -0,0 +1,55 @@ +postValues['additionalSearchPath']) && $this->postValues['additionalSearchPath'] !== '') { + $additionalPath = $this->postValues['additionalSearchPath']; + } + return $additionalPath; + } +} diff --git a/Classes/Configuration/Image/ImageMagick6Preset.php b/Classes/Configuration/Image/ImageMagick6Preset.php new file mode 100644 index 0000000..4010e26 --- /dev/null +++ b/Classes/Configuration/Image/ImageMagick6Preset.php @@ -0,0 +1,102 @@ + true, + // processor_path is determined and set by path lookup methods + 'GFX/processor_path' => '', + 'GFX/processor' => 'ImageMagick', + 'GFX/processor_effects' => true, + ]; + + /** + * Find executable in path, wrapper for specific ImageMagick/GraphicsMagick find methods. + * + * @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'; + } + + $binaryPath = $path . $executable; + if (!file_exists($binaryPath) || !is_executable($binaryPath)) { + continue; + } + + $command = escapeshellarg($binaryPath) . ' -version'; + $executingResult = []; + 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 (is_string($firstResultLine) && str_contains($firstResultLine, 'ImageMagick')) { + [, $version] = explode('ImageMagick', $firstResultLine); + // Example: "6.6.0-4" + [$version] = explode(' ', trim($version)); + if (version_compare($version, '6.0.0') >= 0) { + $this->foundPath = $path; + $result = true; + break; + } + } + } + return $result; + } +} diff --git a/Classes/Configuration/Mail/CustomPreset.php b/Classes/Configuration/Mail/CustomPreset.php new file mode 100644 index 0000000..88e2bb2 --- /dev/null +++ b/Classes/Configuration/Mail/CustomPreset.php @@ -0,0 +1,42 @@ + '', + 'MAIL/transport_sendmail_command' => '', + 'MAIL/transport_smtp_server' => '', + 'MAIL/transport_smtp_encrypt' => '', + 'MAIL/transport_smtp_username' => '', + 'MAIL/transport_smtp_password' => '', + ]; + + protected $readonlyConfigurationValues = [ + 'MAIL/transport_sendmail_command' => true, + ]; +} diff --git a/Classes/Configuration/Mail/MailFeature.php b/Classes/Configuration/Mail/MailFeature.php new file mode 100644 index 0000000..56f281a --- /dev/null +++ b/Classes/Configuration/Mail/MailFeature.php @@ -0,0 +1,40 @@ + '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'] ?? false) === '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(); + } +} diff --git a/Classes/Configuration/Mail/SmtpPreset.php b/Classes/Configuration/Mail/SmtpPreset.php new file mode 100644 index 0000000..ed65d6a --- /dev/null +++ b/Classes/Configuration/Mail/SmtpPreset.php @@ -0,0 +1,77 @@ + '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; + } +} diff --git a/Classes/Configuration/PasswordHashing/Argon2iPreset.php b/Classes/Configuration/PasswordHashing/Argon2iPreset.php new file mode 100644 index 0000000..4437460 --- /dev/null +++ b/Classes/Configuration/PasswordHashing/Argon2iPreset.php @@ -0,0 +1,57 @@ + Argon2iPasswordHash::class, + 'BE/passwordHashing/options' => [], + 'FE/passwordHashing/className' => Argon2iPasswordHash::class, + 'FE/passwordHashing/options' => [], + ]; + + /** + * Find out if Argon2i is available on this system + */ + public function isAvailable(): bool + { + return GeneralUtility::makeInstance(Argon2iPasswordHash::class)->isAvailable(); + } +} diff --git a/Classes/Configuration/PasswordHashing/Argon2idPreset.php b/Classes/Configuration/PasswordHashing/Argon2idPreset.php new file mode 100644 index 0000000..dd89a47 --- /dev/null +++ b/Classes/Configuration/PasswordHashing/Argon2idPreset.php @@ -0,0 +1,57 @@ + Argon2idPasswordHash::class, + 'BE/passwordHashing/options' => [], + 'FE/passwordHashing/className' => Argon2idPasswordHash::class, + 'FE/passwordHashing/options' => [], + ]; + + /** + * Find out if Argon2id is available on this system + */ + public function isAvailable(): bool + { + return GeneralUtility::makeInstance(Argon2idPasswordHash::class)->isAvailable(); + } +} diff --git a/Classes/Configuration/PasswordHashing/BcryptPreset.php b/Classes/Configuration/PasswordHashing/BcryptPreset.php new file mode 100644 index 0000000..e1eec8e --- /dev/null +++ b/Classes/Configuration/PasswordHashing/BcryptPreset.php @@ -0,0 +1,57 @@ + BcryptPasswordHash::class, + 'BE/passwordHashing/options' => [], + 'FE/passwordHashing/className' => BcryptPasswordHash::class, + 'FE/passwordHashing/options' => [], + ]; + + /** + * Find out if bcrypt is available on this system + */ + public function isAvailable(): bool + { + return GeneralUtility::makeInstance(BcryptPasswordHash::class)->isAvailable(); + } +} diff --git a/Classes/Configuration/PasswordHashing/CustomPreset.php b/Classes/Configuration/PasswordHashing/CustomPreset.php new file mode 100644 index 0000000..8e01fb6 --- /dev/null +++ b/Classes/Configuration/PasswordHashing/CustomPreset.php @@ -0,0 +1,67 @@ + $this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className'), + 'readonly' => true, + ]; + $options = (array)$this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/options'); + foreach ($options as $optionName => $optionValue) { + $configurationValues['BE/passwordHashing/options/' . $optionName] = [ + 'value' => $optionValue, + 'readonly' => true, + ]; + } + $configurationValues['FE/passwordHashing/className'] = [ + 'value' => $this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className'), + 'readonly' => true, + ]; + $options = (array)$this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/options'); + foreach ($options as $optionName => $optionValue) { + $configurationValues['FE/passwordHashing/options/' . $optionName] = [ + 'value' => $optionValue, + 'readonly' => true, + ]; + } + return $configurationValues; + } +} diff --git a/Classes/Configuration/PasswordHashing/PasswordHashingFeature.php b/Classes/Configuration/PasswordHashing/PasswordHashingFeature.php new file mode 100644 index 0000000..a4579bf --- /dev/null +++ b/Classes/Configuration/PasswordHashing/PasswordHashingFeature.php @@ -0,0 +1,45 @@ + Pbkdf2PasswordHash::class, + 'BE/passwordHashing/options' => [], + 'FE/passwordHashing/className' => Pbkdf2PasswordHash::class, + 'FE/passwordHashing/options' => [], + ]; + + /** + * Find out if PBKDF2 is available on this system + */ + public function isAvailable(): bool + { + return GeneralUtility::makeInstance(Pbkdf2PasswordHash::class)->isAvailable(); + } +} diff --git a/Classes/Configuration/PasswordHashing/PhpassPreset.php b/Classes/Configuration/PasswordHashing/PhpassPreset.php new file mode 100644 index 0000000..b12d461 --- /dev/null +++ b/Classes/Configuration/PasswordHashing/PhpassPreset.php @@ -0,0 +1,57 @@ + PhpassPasswordHash::class, + 'BE/passwordHashing/options' => [], + 'FE/passwordHashing/className' => PhpassPasswordHash::class, + 'FE/passwordHashing/options' => [], + ]; + + /** + * Find out if Phpass is available on this system + */ + public function isAvailable(): bool + { + return GeneralUtility::makeInstance(PhpassPasswordHash::class)->isAvailable(); + } +} diff --git a/Classes/Configuration/PresetInterface.php b/Classes/Configuration/PresetInterface.php new file mode 100644 index 0000000..d2ca506 --- /dev/null +++ b/Classes/Configuration/PresetInterface.php @@ -0,0 +1,82 @@ + ['EXT:install/Resources/Private/Templates'], + 'partialRootPaths' => ['EXT:install/Resources/Private/Partials'], + 'layoutRootPaths' => ['EXT:install/Resources/Private/Layouts'], + ]; + $renderingContext = GeneralUtility::makeInstance(RenderingContextFactory::class)->create($templatePaths, $request); + $fluidView = new FluidTemplateView($renderingContext); + $view = new FluidViewAdapter($fluidView); + $view->assignMultiple([ + 'controller' => $request->getQueryParams()['install']['controller'] ?? 'maintenance', + 'context' => $request->getQueryParams()['install']['context'] ?? 'install', + 'composerMode' => Environment::isComposerMode(), + 'currentTypo3Version' => (string)(new Typo3Version()), + 'colorScheme' => $request->getQueryParams()['install']['colorScheme'] ?? '', + 'theme' => $request->getQueryParams()['install']['theme'] ?? '', + 'siteName' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? '', + ]); + return $view; + } +} diff --git a/Classes/Controller/BackendModuleController.php b/Classes/Controller/BackendModuleController.php new file mode 100644 index 0000000..11c639d --- /dev/null +++ b/Classes/Controller/BackendModuleController.php @@ -0,0 +1,128 @@ +setAuthorizedAndRedirect('maintenance', $request); + } + + /** + * Initialize session and redirect to "settings" + */ + public function settingsAction(ServerRequestInterface $request): ResponseInterface + { + return $this->setAuthorizedAndRedirect('settings', $request); + } + + /** + * Initialize session and redirect to "upgrade" + */ + public function upgradeAction(ServerRequestInterface $request): ResponseInterface + { + return $this->setAuthorizedAndRedirect('upgrade', $request); + } + + /** + * Initialize session and redirect to "environment" + */ + public function environmentAction(ServerRequestInterface $request): ResponseInterface + { + return $this->setAuthorizedAndRedirect('environment', $request); + } + + /** + * Starts / updates the session and redirects to the Install Tool + * with given action. + */ + protected function setAuthorizedAndRedirect(string $controller, ServerRequestInterface $request): ResponseInterface + { + $redirectParameters = [ + 'install' => [ + 'controller' => $controller, + 'context' => 'backend', + ], + ]; + + $backendUser = $this->getBackendUser(); + $userTS = $backendUser->getTSConfig(); + + $themeDisabled = $userTS['setup.']['fields.']['theme.']['disabled'] ?? '0'; + $theme = $GLOBALS['BE_USER']->uc['theme'] ?? $userTS['setup.']['fields.']['theme'] ?? 'auto'; + if ($themeDisabled === '1') { + $theme = $userTS['setup.']['fields.']['theme'] ?? 'modern'; + } + if ($theme !== 'modern') { + $redirectParameters['install']['theme'] = $theme; + } + + $colorSchemeDisabled = $userTS['setup.']['fields.']['colorScheme.']['disabled'] ?? '0'; + $colorScheme = $GLOBALS['BE_USER']->uc['colorScheme'] ?? $userTS['setup.']['fields.']['colorScheme'] ?? 'auto'; + if ($colorSchemeDisabled === '1') { + $colorScheme = $userTS['setup.']['fields.']['colorScheme'] ?? 'light'; + } + if ($colorScheme !== 'auto') { + $redirectParameters['install']['colorScheme'] = $colorScheme; + } + + $userSession = $this->getBackendUser()->getSession(); + $this->sessionService->installSessionHandler($request); + $this->sessionService->startSession(); + $this->sessionService->setAuthorizedBackendSession($userSession, $this->sessionBackend); + $normalizedParams = $request->getAttribute('normalizedParams'); + $redirectLocation = $normalizedParams->getSiteUrl() . '?__typo3_install&' . http_build_query($redirectParameters, '', '&', PHP_QUERY_RFC3986); + return new RedirectResponse($redirectLocation, 303); + } + + protected function getBackendUser(): BackendUserAuthentication + { + return $GLOBALS['BE_USER']; + } +} diff --git a/Classes/Controller/ControllerTrait.php b/Classes/Controller/ControllerTrait.php new file mode 100644 index 0000000..cea34d2 --- /dev/null +++ b/Classes/Controller/ControllerTrait.php @@ -0,0 +1,46 @@ +default(SourceKeyword::self) + // script-src 'nonce-...' required for importmaps + ->extend(Directive::ScriptSrc, SourceKeyword::nonceProxy) + // `style-src 'unsafe-inline'` required for lit in safari and firefox to allow inline