‪TYPO3CMS  ‪main
CoreUpdateService.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
29 
42 {
46  protected ‪$messages;
47 
53  protected ‪$downloadTargetPath;
54 
60  protected ‪$symlinkToCoreFiles;
61 
67  protected ‪$downloadBaseUri;
68 
69  public function ‪__construct(protected readonly CoreVersionService $coreVersionService)
70  {
71  $this->‪setDownloadTargetPath(‪Environment::getVarPath() . '/transient/');
72  $this->symlinkToCoreFiles = $this->‪discoverCurrentCoreSymlink();
73  $this->downloadBaseUri = 'https://get.typo3.org';
74  $this->messages = new ‪FlashMessageQueue('install');
75  }
76 
82  public function ‪isCoreUpdateEnabled()
83  {
84  $coreUpdateDisabled = getenv('TYPO3_DISABLE_CORE_UPDATER') ?: (getenv('REDIRECT_TYPO3_DISABLE_CORE_UPDATER') ?: false);
85  return !‪Environment::isComposerMode() && !$coreUpdateDisabled;
86  }
87 
93  protected function ‪discoverCurrentCoreSymlink()
94  {
95  return ‪Environment::getPublicPath() . '/typo3_src';
96  }
97 
105  {
106  if (!is_dir(‪$downloadTargetPath)) {
108  }
109  $this->downloadTargetPath = ‪$downloadTargetPath;
110  }
111 
115  public function ‪getMessages(): ‪FlashMessageQueue
116  {
117  return ‪$this->messages;
118  }
119 
127  public function ‪checkPreConditions(‪CoreRelease $coreRelease, ‪WebserverType $webserverType)
128  {
129  $success = true;
130 
131  // Folder structure test: Update can be done only if folder structure returns no errors
132  $folderStructureFacade = GeneralUtility::makeInstance(DefaultFactory::class)->getStructure($webserverType);
133  $folderStructureMessageQueue = $folderStructureFacade->getStatus();
134  $folderStructureErrors = $folderStructureMessageQueue->getAllMessages(ContextualFeedbackSeverity::ERROR);
135  $folderStructureWarnings = $folderStructureMessageQueue->getAllMessages(ContextualFeedbackSeverity::WARNING);
136  if (!empty($folderStructureErrors) || !empty($folderStructureWarnings) || !is_link(‪Environment::getPublicPath() . '/typo3_src')) {
137  $success = false;
138  $this->messages->enqueue(new ‪FlashMessage(
139  'To perform an update, the folder structure of this TYPO3 CMS instance must'
140  . ' stick to the conventions, or the update process could lead to unexpected results'
141  . ' and may be hazardous to your system. Please check your directory status in the'
142  . ' “Environment” module under “Directory Status”.',
143  'Automatic TYPO3 CMS core update not possible: Folder structure has errors or warnings',
144  ContextualFeedbackSeverity::ERROR
145  ));
146  }
147 
148  // No core update on windows
150  $success = false;
151  $this->messages->enqueue(new FlashMessage(
152  '',
153  'Automatic TYPO3 CMS core update not possible: Update not supported on Windows OS',
154  ContextualFeedbackSeverity::ERROR
155  ));
156  }
157 
158  if ($success) {
159  // Explicit write check to document root
160  $file = ‪Environment::getPublicPath() . '/' . ‪StringUtility::getUniqueId('install-core-update-test-');
161  $result = @touch($file);
162  if (!$result) {
163  $success = false;
164  $this->messages->enqueue(new FlashMessage(
165  'Could not write a file in path "' . ‪Environment::getPublicPath() . '/"!'
166  . ' Please check your directory status in the “Environment” module under “Directory Status”.',
167  'Automatic TYPO3 CMS core update not possible: No write access to document root',
168  ContextualFeedbackSeverity::ERROR
169  ));
170  } else {
171  // Check symlink creation
172  $link = ‪Environment::getPublicPath() . '/' . ‪StringUtility::getUniqueId('install-core-update-test-');
173  @symlink($file, $link);
174  if (!is_link($link)) {
175  $success = false;
176  $this->messages->enqueue(new FlashMessage(
177  'Could not create a symbolic link in path "' . ‪Environment::getPublicPath() . '/"!'
178  . ' Please check your directory status in the “Environment” module under “Directory Status”.',
179  'Automatic TYPO3 CMS core update not possible: No symlink creation possible',
180  ContextualFeedbackSeverity::ERROR
181  ));
182  } else {
183  unlink($link);
184  }
185  unlink($file);
186  }
187 
188  if (!$this->‪checkCoreFilesAvailable($coreRelease->‪getVersion())) {
189  // Explicit write check to upper directory of current core location
190  $coreLocation = @realpath($this->symlinkToCoreFiles . '/../');
191  $file = $coreLocation . '/' . ‪StringUtility::getUniqueId('install-core-update-test-');
192  $result = @touch($file);
193  if (!$result) {
194  $success = false;
195  $this->messages->enqueue(new FlashMessage(
196  'New TYPO3 CMS core should be installed in "' . $coreLocation . '", but this directory is not writable!'
197  . ' Please check your directory status in the “Environment” module under “Directory Status”.',
198  'Automatic TYPO3 CMS core update not possible: No write access to TYPO3 CMS core location',
199  ContextualFeedbackSeverity::ERROR
200  ));
201  } else {
202  unlink($file);
203  }
204  }
205  }
206 
207  if ($success && !$this->coreVersionService->isInstalledVersionAReleasedVersion()) {
208  $success = false;
209  $this->messages->enqueue(new FlashMessage(
210  'Your current version is specified as ' . $this->coreVersionService->getInstalledVersion() . '.'
211  . ' This is a development version and can not be updated automatically. If this is a "git"'
212  . ' checkout, please update using git directly.',
213  'Automatic TYPO3 CMS core update not possible: You are running a development version of TYPO3',
214  ContextualFeedbackSeverity::ERROR
215  ));
216  }
217 
218  return $success;
219  }
220 
227  public function ‪downloadVersion(CoreRelease $coreRelease)
228  {
229  $version = $coreRelease->getVersion();
230  $success = true;
231  if ($this->‪checkCoreFilesAvailable($version)) {
232  $this->messages->enqueue(new FlashMessage(
233  '',
234  'Skipped download of TYPO3 CMS core. A core source directory already exists in destination path. Using this instead.',
235  ContextualFeedbackSeverity::NOTICE
236  ));
237  } else {
238  $downloadUri = $this->downloadBaseUri . '/' . $version;
239  $fileLocation = $this->‪getDownloadTarGzTargetPath($version);
240 
241  if (@file_exists($fileLocation)) {
242  $success = false;
243  $this->messages->enqueue(new FlashMessage(
244  '',
245  'TYPO3 CMS core download exists in download location: ' . ‪PathUtility::stripPathSitePrefix($this->downloadTargetPath),
246  ContextualFeedbackSeverity::ERROR
247  ));
248  } else {
249  $fileContent = ‪GeneralUtility::getUrl($downloadUri);
250  if (!$fileContent) {
251  $success = false;
252  $this->messages->enqueue(new FlashMessage(
253  'Failed to download ' . $downloadUri,
254  'Download not successful',
255  ContextualFeedbackSeverity::ERROR
256  ));
257  } else {
258  $fileStoreResult = file_put_contents($fileLocation, $fileContent);
259  if (!$fileStoreResult) {
260  $success = false;
261  $this->messages->enqueue(new FlashMessage(
262  '',
263  'Unable to store download content',
264  ContextualFeedbackSeverity::ERROR
265  ));
266  } else {
267  $this->messages->enqueue(new FlashMessage(
268  '',
269  'TYPO3 CMS core download finished'
270  ));
271  }
272  }
273  }
274  }
275  return $success;
276  }
277 
284  public function ‪verifyFileChecksum(CoreRelease $coreRelease)
285  {
286  $version = $coreRelease->getVersion();
287  $success = true;
288  if ($this->‪checkCoreFilesAvailable($version)) {
289  $this->messages->enqueue(new FlashMessage(
290  '',
291  'Verifying existing TYPO3 CMS core checksum is not possible',
292  ContextualFeedbackSeverity::WARNING
293  ));
294  } else {
295  $fileLocation = $this->‪getDownloadTarGzTargetPath($version);
296  $expectedChecksum = $coreRelease->getChecksum();
297  if (!file_exists($fileLocation)) {
298  $success = false;
299  $this->messages->enqueue(new FlashMessage(
300  '',
301  'Downloaded TYPO3 CMS core not found',
302  ContextualFeedbackSeverity::ERROR
303  ));
304  } else {
305  $actualChecksum = sha1_file($fileLocation);
306  if ($actualChecksum !== $expectedChecksum) {
307  $success = false;
308  $this->messages->enqueue(new FlashMessage(
309  'The official TYPO3 CMS version system on https://get.typo3.org expects a sha1 checksum of '
310  . $expectedChecksum . ' from the content of the downloaded new TYPO3 CMS core version ' . $version . '.'
311  . ' The actual checksum is ' . $actualChecksum . '. The update is stopped. This may be a'
312  . ' failed download, an attack, or an issue with the typo3.org infrastructure.',
313  'New TYPO3 CMS core checksum mismatch',
314  ContextualFeedbackSeverity::ERROR
315  ));
316  } else {
317  $this->messages->enqueue(new FlashMessage(
318  '',
319  'Checksum verified'
320  ));
321  }
322  }
323  }
324  return $success;
325  }
326 
333  public function ‪unpackVersion(CoreRelease $coreRelease)
334  {
335  $version = $coreRelease->getVersion();
336  $success = true;
337  if ($this->‪checkCoreFilesAvailable($version)) {
338  $this->messages->enqueue(new FlashMessage(
339  '',
340  'Unpacking TYPO3 CMS core files skipped',
341  ContextualFeedbackSeverity::NOTICE
342  ));
343  } else {
344  $fileLocation = $this->downloadTargetPath . $version . '.tar.gz';
345  if (!@is_file($fileLocation)) {
346  $success = false;
347  $this->messages->enqueue(new FlashMessage(
348  '',
349  'Downloaded TYPO3 CMS core not found',
350  ContextualFeedbackSeverity::ERROR
351  ));
352  } elseif (@file_exists($this->downloadTargetPath . 'typo3_src-' . $version)) {
353  $success = false;
354  $this->messages->enqueue(new FlashMessage(
355  '',
356  'Unpacked TYPO3 CMS core exists in download location: ' . ‪PathUtility::stripPathSitePrefix($this->downloadTargetPath),
357  ContextualFeedbackSeverity::ERROR
358  ));
359  } else {
360  $unpackCommand = 'tar xf ' . escapeshellarg($fileLocation) . ' -C ' . escapeshellarg($this->downloadTargetPath) . ' 2>&1';
361  exec($unpackCommand, ‪$output, $errorCode);
362  if ($errorCode) {
363  $success = false;
364  $this->messages->enqueue(new FlashMessage(
365  '',
366  'Unpacking TYPO3 CMS core not successful',
367  ContextualFeedbackSeverity::ERROR
368  ));
369  } else {
370  $removePackedFileResult = unlink($fileLocation);
371  if (!$removePackedFileResult) {
372  $success = false;
373  $this->messages->enqueue(new FlashMessage(
374  '',
375  'Removing packed TYPO3 CMS core not successful',
376  ContextualFeedbackSeverity::ERROR
377  ));
378  } else {
379  $this->messages->enqueue(new FlashMessage(
380  '',
381  'Unpacking TYPO3 CMS core successful'
382  ));
383  }
384  }
385  }
386  }
387  return $success;
388  }
389 
396  public function ‪moveVersion(CoreRelease $coreRelease)
397  {
398  $version = $coreRelease->getVersion();
399  $success = true;
400  if ($this->‪checkCoreFilesAvailable($version)) {
401  $this->messages->enqueue(new FlashMessage(
402  '',
403  'Moving TYPO3 CMS core files skipped',
404  ContextualFeedbackSeverity::NOTICE
405  ));
406  } else {
407  $downloadedCoreLocation = $this->downloadTargetPath . 'typo3_src-' . $version;
408  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
409 
410  if (!@is_dir($downloadedCoreLocation)) {
411  $success = false;
412  $this->messages->enqueue(new FlashMessage(
413  '',
414  'Unpacked TYPO3 CMS core not found',
415  ContextualFeedbackSeverity::ERROR
416  ));
417  } else {
418  $moveResult = rename($downloadedCoreLocation, $newCoreLocation);
419  if (!$moveResult) {
420  $success = false;
421  $this->messages->enqueue(new FlashMessage(
422  '',
423  'Moving TYPO3 CMS core to ' . $newCoreLocation . ' failed',
424  ContextualFeedbackSeverity::ERROR
425  ));
426  } else {
427  $this->messages->enqueue(new FlashMessage(
428  '',
429  'Moved TYPO3 CMS core to final location'
430  ));
431  }
432  }
433  }
434  return $success;
435  }
436 
443  public function ‪activateVersion(CoreRelease $coreRelease)
444  {
445  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $coreRelease->getVersion();
446  $success = true;
447  if (!is_dir($newCoreLocation)) {
448  $success = false;
449  $this->messages->enqueue(new FlashMessage(
450  '',
451  'New TYPO3 CMS core not found',
452  ContextualFeedbackSeverity::ERROR
453  ));
454  } elseif (!is_link($this->symlinkToCoreFiles)) {
455  $success = false;
456  $this->messages->enqueue(new FlashMessage(
457  '',
458  'TYPO3 CMS core source directory (typo3_src) is not a link',
459  ContextualFeedbackSeverity::ERROR
460  ));
461  } else {
462  $isCurrentCoreSymlinkAbsolute = ‪PathUtility::isAbsolutePath((string)readlink($this->symlinkToCoreFiles));
463  $unlinkResult = unlink($this->symlinkToCoreFiles);
464  if (!$unlinkResult) {
465  $success = false;
466  $this->messages->enqueue(new FlashMessage(
467  '',
468  'Removing old symlink failed',
469  ContextualFeedbackSeverity::ERROR
470  ));
471  } else {
472  if (!$isCurrentCoreSymlinkAbsolute) {
473  $newCoreLocation = $this->‪getRelativePath($newCoreLocation);
474  }
475  $symlinkResult = symlink($newCoreLocation, $this->symlinkToCoreFiles);
476  if ($symlinkResult) {
477  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
478  } else {
479  $success = false;
480  $this->messages->enqueue(new FlashMessage(
481  '',
482  'Linking new TYPO3 CMS core failed',
483  ContextualFeedbackSeverity::ERROR
484  ));
485  }
486  }
487  }
488  return $success;
489  }
490 
497  protected function ‪getDownloadTarGzTargetPath($version)
498  {
499  return $this->downloadTargetPath . $version . '.tar.gz';
500  }
501 
508  protected function ‪getRelativePath($absolutePath)
509  {
510  $sourcePath = explode(DIRECTORY_SEPARATOR, ‪Environment::getPublicPath());
511  $targetPath = explode(DIRECTORY_SEPARATOR, rtrim($absolutePath, DIRECTORY_SEPARATOR));
512  while (count($sourcePath) && count($targetPath) && $sourcePath[0] === $targetPath[0]) {
513  array_shift($sourcePath);
514  array_shift($targetPath);
515  }
516  return str_pad('', count($sourcePath) * 3, '..' . DIRECTORY_SEPARATOR) . implode(DIRECTORY_SEPARATOR, $targetPath);
517  }
518 
526  protected function ‪checkCoreFilesAvailable($version)
527  {
528  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
529  return @is_dir($newCoreLocation);
530  }
531 }
‪TYPO3\CMS\Install\Service\CoreUpdateService\activateVersion
‪bool activateVersion(CoreRelease $coreRelease)
Definition: CoreUpdateService.php:439
‪TYPO3\CMS\Install\Service\CoreUpdateService\getRelativePath
‪string getRelativePath($absolutePath)
Definition: CoreUpdateService.php:504
‪TYPO3\CMS\Install\Service\CoreUpdateService\unpackVersion
‪bool unpackVersion(CoreRelease $coreRelease)
Definition: CoreUpdateService.php:329
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static stripPathSitePrefix(string $path)
Definition: PathUtility.php:428
‪TYPO3\CMS\Install\Service\CoreUpdateService\verifyFileChecksum
‪bool verifyFileChecksum(CoreRelease $coreRelease)
Definition: CoreUpdateService.php:280
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Utility\PathUtility\isAbsolutePath
‪static isAbsolutePath(string $path)
Definition: PathUtility.php:286
‪TYPO3\CMS\Core\Core\Environment\isComposerMode
‪static isComposerMode()
Definition: Environment.php:137
‪TYPO3\CMS\Install\Service\CoreUpdateService\$downloadBaseUri
‪string $downloadBaseUri
Definition: CoreUpdateService.php:63
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Install\Service\CoreUpdateService\$symlinkToCoreFiles
‪string $symlinkToCoreFiles
Definition: CoreUpdateService.php:57
‪TYPO3\CMS\Install\Service\CoreUpdateService\downloadVersion
‪bool downloadVersion(CoreRelease $coreRelease)
Definition: CoreUpdateService.php:223
‪TYPO3\CMS\Install\FolderStructure\DefaultFactory
Definition: DefaultFactory.php:26
‪TYPO3\CMS\Install\Service\CoreUpdateService\checkCoreFilesAvailable
‪bool checkCoreFilesAvailable($version)
Definition: CoreUpdateService.php:522
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep(string $directory)
Definition: GeneralUtility.php:1654
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static getVarPath()
Definition: Environment.php:197
‪TYPO3\CMS\Install\CoreVersion\CoreRelease
Definition: CoreRelease.php:21
‪TYPO3\CMS\Install\WebserverType
‪WebserverType
Definition: WebserverType.php:26
‪TYPO3\CMS\Install\Service\CoreUpdateService\checkPreConditions
‪bool checkPreConditions(CoreRelease $coreRelease, WebserverType $webserverType)
Definition: CoreUpdateService.php:123
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Install\Service\CoreUpdateService\isCoreUpdateEnabled
‪bool isCoreUpdateEnabled()
Definition: CoreUpdateService.php:78
‪TYPO3\CMS\Install\Service\CoreUpdateService\discoverCurrentCoreSymlink
‪string discoverCurrentCoreSymlink()
Definition: CoreUpdateService.php:89
‪TYPO3\CMS\Install\Service\CoreUpdateService\$messages
‪FlashMessageQueue $messages
Definition: CoreUpdateService.php:45
‪TYPO3\CMS\Core\Utility\GeneralUtility\getUrl
‪static string false getUrl(string $url)
Definition: GeneralUtility.php:1444
‪TYPO3\CMS\Install\Service\CoreUpdateService\getMessages
‪getMessages()
Definition: CoreUpdateService.php:111
‪TYPO3\CMS\Install\CoreVersion\CoreRelease\getChecksum
‪getChecksum()
Definition: CoreRelease.php:53
‪TYPO3\CMS\Core\Service\OpcodeCacheService
Definition: OpcodeCacheService.php:27
‪TYPO3\CMS\Install\Service\CoreUpdateService\moveVersion
‪bool moveVersion(CoreRelease $coreRelease)
Definition: CoreUpdateService.php:392
‪$output
‪$output
Definition: annotationChecker.php:114
‪TYPO3\CMS\Install\Service\CoreUpdateService\getDownloadTarGzTargetPath
‪string getDownloadTarGzTargetPath($version)
Definition: CoreUpdateService.php:493
‪TYPO3\CMS\Install\Service\CoreUpdateService
Definition: CoreUpdateService.php:42
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:27
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Install\Service\CoreUpdateService\setDownloadTargetPath
‪setDownloadTargetPath($downloadTargetPath)
Definition: CoreUpdateService.php:100
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Install\CoreVersion\CoreRelease\getVersion
‪getVersion()
Definition: CoreRelease.php:38
‪TYPO3\CMS\Core\Messaging\FlashMessageQueue
Definition: FlashMessageQueue.php:29
‪TYPO3\CMS\Install\Service
Definition: ClearCacheService.php:16
‪TYPO3\CMS\Install\Service\CoreUpdateService\__construct
‪__construct(protected readonly CoreVersionService $coreVersionService)
Definition: CoreUpdateService.php:65
‪TYPO3\CMS\Install\Service\CoreUpdateService\$downloadTargetPath
‪string $downloadTargetPath
Definition: CoreUpdateService.php:51
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static getUniqueId(string $prefix='')
Definition: StringUtility.php:57
‪TYPO3\CMS\Core\Core\Environment\isWindows
‪static isWindows()
Definition: Environment.php:276