[BUGFIX] Ignore wizards with no upgrades in report
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Report / InstallStatusReport.php
1 <?php
2 namespace TYPO3\CMS\Install\Report;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Core\Environment;
18 use TYPO3\CMS\Core\Localization\LanguageService;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Install\Service\Exception;
21 use TYPO3\CMS\Install\Service\UpgradeWizardsService;
22 use TYPO3\CMS\Reports\Status;
23
24 /**
25 * Provides an installation status report.
26 * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
27 */
28 class InstallStatusReport implements \TYPO3\CMS\Reports\StatusProviderInterface
29 {
30 /**
31 * Compiles a collection of system status checks as a status report.
32 *
33 * @return Status[]
34 */
35 public function getStatus()
36 {
37 return [
38 'FileSystem' => $this->getFileSystemStatus(),
39 'RemainingUpdates' => $this->getRemainingUpdatesStatus(),
40 'NewVersion' => $this->getNewVersionStatus(),
41 ];
42 }
43
44 /**
45 * Checks for several directories being writable.
46 *
47 * @return Status Indicates status of the file system
48 */
49 protected function getFileSystemStatus()
50 {
51 $languageService = $this->getLanguageService();
52 $value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_writable');
53 $message = '';
54 $severity = Status::OK;
55 // Requirement level
56 // -1 = not required, but if it exists may be writable or not
57 // 0 = not required, if it exists the dir should be writable
58 // 1 = required, doesn't have to be writable
59 // 2 = required, has to be writable
60 $varPath = Environment::getVarPath();
61 $sitePath = Environment::getPublicPath();
62 $rootPath = Environment::getProjectPath();
63 $checkWritable = [
64 $sitePath . '/typo3temp/' => 2,
65 $sitePath . '/typo3temp/assets/' => 2,
66 $sitePath . '/typo3temp/assets/compressed/' => 2,
67 // only needed when GraphicalFunctions is used
68 $sitePath . '/typo3temp/assets/images/' => 0,
69 // used in PageGenerator (inlineStyle2Temp) and Backend + Language JS files
70 $sitePath . '/typo3temp/assets/css/' => 2,
71 $sitePath . '/typo3temp/assets/js/' => 2,
72 // fallback storage of FAL
73 $sitePath . '/typo3temp/assets/_processed_/' => 0,
74 $varPath => 2,
75 $varPath . '/transient/' => 2,
76 $varPath . '/charset/' => 2,
77 $varPath . '/lock/' => 2,
78 $sitePath . '/typo3conf/' => 2,
79 Environment::getLabelsPath() => 0,
80 $sitePath . '/' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] => -1,
81 $sitePath . '/' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . '_temp_/' => 0,
82 ];
83
84 // Check for writable extension folder files in non-composer mode only
85 if (!Environment::isComposerMode()) {
86 $checkWritable[Environment::getExtensionsPath()] = 0;
87 if ($GLOBALS['TYPO3_CONF_VARS']['EXT']['allowGlobalInstall']) {
88 $checkWritable[Environment::getBackendPath() . '/ext/'] = -1;
89 }
90 }
91
92 foreach ($checkWritable as $path => $requirementLevel) {
93 $relPath = substr($path, strlen($rootPath) + 1);
94 if (!@is_dir($path)) {
95 // If the directory is missing, try to create it
96 GeneralUtility::mkdir($path);
97 }
98 if (!@is_dir($path)) {
99 if ($requirementLevel > 0) {
100 // directory is required
101 $value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_missingDirectory');
102 $message .= sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryDoesNotExistCouldNotCreate'), $relPath) . '<br />';
103 $severity = Status::ERROR;
104 } else {
105 $message .= sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryDoesNotExist'), $relPath);
106 if ($requirementLevel == 0) {
107 $message .= ' ' . $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryShouldAlsoBeWritable');
108 }
109 $message .= '<br />';
110 if ($severity < Status::WARNING) {
111 $value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_nonExistingDirectory');
112 $severity = Status::WARNING;
113 }
114 }
115 } else {
116 if (!is_writable($path)) {
117 switch ($requirementLevel) {
118 case 0:
119 $message .= sprintf(
120 $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryShouldBeWritable'),
121 $path
122 ) . '<br />';
123 if ($severity < Status::WARNING) {
124 $value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_recommendedWritableDirectory');
125 $severity = Status::WARNING;
126 }
127 break;
128 case 2:
129 $value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_requiredWritableDirectory');
130 $message .= sprintf(
131 $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryMustBeWritable'),
132 $path
133 ) . '<br />';
134 $severity = Status::ERROR;
135 break;
136 default:
137 }
138 }
139 }
140 }
141 return GeneralUtility::makeInstance(Status::class, $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_fileSystem'), $value, $message, $severity);
142 }
143
144 /**
145 * Returns all incomplete update wizards.
146 *
147 * Fetches all wizards that are not marked "done" in the registry and filters out
148 * the ones that should not be rendered (= no upgrade required).
149 *
150 * @return array
151 */
152 protected function getIncompleteWizards(): array
153 {
154 $upgradeWizardsService = GeneralUtility::makeInstance(UpgradeWizardsService::class);
155 $incompleteWizards = $upgradeWizardsService->getUpgradeWizardsList();
156 $incompleteWizards = array_filter(
157 $incompleteWizards,
158 function ($wizard) {
159 return $wizard['shouldRenderWizard'];
160 }
161 );
162 return $incompleteWizards;
163 }
164
165 /**
166 * Checks if there are still updates to perform
167 *
168 * @return Status Represents whether the installation is completely updated yet
169 */
170 protected function getRemainingUpdatesStatus()
171 {
172 $languageService = $this->getLanguageService();
173 $value = $languageService->getLL('status_updateComplete');
174 $message = '';
175 $severity = Status::OK;
176 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
177 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
178 // check if there are update wizards left to perform
179 $incompleteWizards = $this->getIncompleteWizards();
180 if (count($incompleteWizards)) {
181 // At least one incomplete wizard was found
182 $value = $languageService->getLL('status_updateIncomplete');
183 $severity = Status::WARNING;
184 $url = (string)$uriBuilder->buildUriFromRoute('tools_toolsupgrade');
185 $message = sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_update'), '<a href="' . htmlspecialchars($url) . '">', '</a>');
186 }
187
188 return GeneralUtility::makeInstance(Status::class, $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_remainingUpdates'), $value, $message, $severity);
189 }
190
191 /**
192 * Checks if there is a new minor TYPO3 version to update to.
193 *
194 * @return Status Represents whether there is a new version available online
195 */
196 protected function getNewVersionStatus()
197 {
198 $languageService = $this->getLanguageService();
199 /** @var \TYPO3\CMS\Install\Service\CoreVersionService $coreVersionService */
200 $coreVersionService = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\CoreVersionService::class);
201
202 // No updates for development versions
203 if (!$coreVersionService->isInstalledVersionAReleasedVersion()) {
204 return GeneralUtility::makeInstance(Status::class, 'TYPO3', TYPO3_version, $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_isDevelopmentVersion'), Status::NOTICE);
205 }
206
207 try {
208 $isUpdateAvailable = $coreVersionService->isYoungerPatchReleaseAvailable();
209 $isMaintainedVersion = $coreVersionService->isVersionActivelyMaintained();
210 } catch (Exception\RemoteFetchException $remoteFetchException) {
211 return GeneralUtility::makeInstance(
212 Status::class,
213 'TYPO3',
214 TYPO3_version,
215 $languageService->sL(
216 'LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_remoteFetchException'
217 ),
218 Status::NOTICE
219 );
220 }
221
222 if (!$isUpdateAvailable && $isMaintainedVersion) {
223 // Everything is fine, working with the latest version
224 $message = '';
225 $status = Status::OK;
226 } elseif ($isUpdateAvailable) {
227 // There is an update available
228 $newVersion = $coreVersionService->getYoungestPatchRelease();
229 if ($coreVersionService->isUpdateSecurityRelevant()) {
230 $message = sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_newVersionSecurityRelevant'), $newVersion);
231 $status = Status::ERROR;
232 } else {
233 $message = sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_newVersion'), $newVersion);
234 $status = Status::WARNING;
235 }
236 } else {
237 // Version is not maintained
238 $message = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_versionOutdated');
239 $status = Status::ERROR;
240 }
241
242 return GeneralUtility::makeInstance(Status::class, 'TYPO3', TYPO3_version, $message, $status);
243 }
244
245 /**
246 * @return LanguageService
247 */
248 protected function getLanguageService()
249 {
250 return $GLOBALS['LANG'];
251 }
252 }