‪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 ‪$coreVersionService;
47 
51  protected ‪$messages;
52 
58  protected ‪$downloadTargetPath;
59 
65  protected ‪$symlinkToCoreFiles;
66 
72  protected ‪$downloadBaseUri;
73 
74  public function ‪__construct(CoreVersionService ‪$coreVersionService)
75  {
76  $this->coreVersionService = ‪$coreVersionService;
77  $this->‪setDownloadTargetPath(‪Environment::getVarPath() . '/transient/');
78  $this->symlinkToCoreFiles = $this->‪discoverCurrentCoreSymlink();
79  $this->downloadBaseUri = 'https://get.typo3.org';
80  $this->messages = new ‪FlashMessageQueue('install');
81  }
82 
88  public function ‪isCoreUpdateEnabled()
89  {
90  $coreUpdateDisabled = getenv('TYPO3_DISABLE_CORE_UPDATER') ?: (getenv('REDIRECT_TYPO3_DISABLE_CORE_UPDATER') ?: false);
91  return !‪Environment::isComposerMode() && !$coreUpdateDisabled;
92  }
93 
99  protected function ‪discoverCurrentCoreSymlink()
100  {
101  return ‪Environment::getPublicPath() . '/typo3_src';
102  }
103 
111  {
112  if (!is_dir(‪$downloadTargetPath)) {
114  }
115  $this->downloadTargetPath = ‪$downloadTargetPath;
116  }
117 
121  public function ‪getMessages(): ‪FlashMessageQueue
122  {
123  return ‪$this->messages;
124  }
125 
133  public function ‪checkPreConditions(CoreRelease $coreRelease, ‪WebserverType $webserverType)
134  {
135  $success = true;
136 
137  // Folder structure test: Update can be done only if folder structure returns no errors
138  $folderStructureFacade = GeneralUtility::makeInstance(DefaultFactory::class)->getStructure($webserverType);
139  $folderStructureMessageQueue = $folderStructureFacade->getStatus();
140  $folderStructureErrors = $folderStructureMessageQueue->getAllMessages(ContextualFeedbackSeverity::ERROR);
141  $folderStructureWarnings = $folderStructureMessageQueue->getAllMessages(ContextualFeedbackSeverity::WARNING);
142  if (!empty($folderStructureErrors) || !empty($folderStructureWarnings) || !is_link(‪Environment::getPublicPath() . '/typo3_src')) {
143  $success = false;
144  $this->messages->enqueue(new FlashMessage(
145  'To perform an update, the folder structure of this TYPO3 CMS instance must'
146  . ' stick to the conventions, or the update process could lead to unexpected results'
147  . ' and may be hazardous to your system. Please check your directory status in the'
148  . ' “Environment” module under “Directory Status”.',
149  'Automatic TYPO3 CMS core update not possible: Folder structure has errors or warnings',
150  ContextualFeedbackSeverity::ERROR
151  ));
152  }
153 
154  // No core update on windows
156  $success = false;
157  $this->messages->enqueue(new FlashMessage(
158  '',
159  'Automatic TYPO3 CMS core update not possible: Update not supported on Windows OS',
160  ContextualFeedbackSeverity::ERROR
161  ));
162  }
163 
164  if ($success) {
165  // Explicit write check to document root
166  $file = ‪Environment::getPublicPath() . '/' . ‪StringUtility::getUniqueId('install-core-update-test-');
167  $result = @touch($file);
168  if (!$result) {
169  $success = false;
170  $this->messages->enqueue(new FlashMessage(
171  'Could not write a file in path "' . ‪Environment::getPublicPath() . '/"!'
172  . ' Please check your directory status in the “Environment” module under “Directory Status”.',
173  'Automatic TYPO3 CMS core update not possible: No write access to document root',
174  ContextualFeedbackSeverity::ERROR
175  ));
176  } else {
177  // Check symlink creation
178  $link = ‪Environment::getPublicPath() . '/' . ‪StringUtility::getUniqueId('install-core-update-test-');
179  @symlink($file, $link);
180  if (!is_link($link)) {
181  $success = false;
182  $this->messages->enqueue(new FlashMessage(
183  'Could not create a symbolic link in path "' . ‪Environment::getPublicPath() . '/"!'
184  . ' Please check your directory status in the “Environment” module under “Directory Status”.',
185  'Automatic TYPO3 CMS core update not possible: No symlink creation possible',
186  ContextualFeedbackSeverity::ERROR
187  ));
188  } else {
189  unlink($link);
190  }
191  unlink($file);
192  }
193 
194  if (!$this->‪checkCoreFilesAvailable($coreRelease->getVersion())) {
195  // Explicit write check to upper directory of current core location
196  $coreLocation = @realpath($this->symlinkToCoreFiles . '/../');
197  $file = $coreLocation . '/' . ‪StringUtility::getUniqueId('install-core-update-test-');
198  $result = @touch($file);
199  if (!$result) {
200  $success = false;
201  $this->messages->enqueue(new FlashMessage(
202  'New TYPO3 CMS core should be installed in "' . $coreLocation . '", but this directory is not writable!'
203  . ' Please check your directory status in the “Environment” module under “Directory Status”.',
204  'Automatic TYPO3 CMS core update not possible: No write access to TYPO3 CMS core location',
205  ContextualFeedbackSeverity::ERROR
206  ));
207  } else {
208  unlink($file);
209  }
210  }
211  }
212 
213  if ($success && !$this->coreVersionService->isInstalledVersionAReleasedVersion()) {
214  $success = false;
215  $this->messages->enqueue(new FlashMessage(
216  'Your current version is specified as ' . $this->coreVersionService->getInstalledVersion() . '.'
217  . ' This is a development version and can not be updated automatically. If this is a "git"'
218  . ' checkout, please update using git directly.',
219  'Automatic TYPO3 CMS core update not possible: You are running a development version of TYPO3',
220  ContextualFeedbackSeverity::ERROR
221  ));
222  }
223 
224  return $success;
225  }
226 
233  public function ‪downloadVersion(CoreRelease $coreRelease)
234  {
235  $version = $coreRelease->getVersion();
236  $success = true;
237  if ($this->‪checkCoreFilesAvailable($version)) {
238  $this->messages->enqueue(new FlashMessage(
239  '',
240  'Skipped download of TYPO3 CMS core. A core source directory already exists in destination path. Using this instead.',
241  ContextualFeedbackSeverity::NOTICE
242  ));
243  } else {
244  $downloadUri = $this->downloadBaseUri . '/' . $version;
245  $fileLocation = $this->‪getDownloadTarGzTargetPath($version);
246 
247  if (@file_exists($fileLocation)) {
248  $success = false;
249  $this->messages->enqueue(new FlashMessage(
250  '',
251  'TYPO3 CMS core download exists in download location: ' . ‪PathUtility::stripPathSitePrefix($this->downloadTargetPath),
252  ContextualFeedbackSeverity::ERROR
253  ));
254  } else {
255  $fileContent = ‪GeneralUtility::getUrl($downloadUri);
256  if (!$fileContent) {
257  $success = false;
258  $this->messages->enqueue(new FlashMessage(
259  'Failed to download ' . $downloadUri,
260  'Download not successful',
261  ContextualFeedbackSeverity::ERROR
262  ));
263  } else {
264  $fileStoreResult = file_put_contents($fileLocation, $fileContent);
265  if (!$fileStoreResult) {
266  $success = false;
267  $this->messages->enqueue(new FlashMessage(
268  '',
269  'Unable to store download content',
270  ContextualFeedbackSeverity::ERROR
271  ));
272  } else {
273  $this->messages->enqueue(new FlashMessage(
274  '',
275  'TYPO3 CMS core download finished'
276  ));
277  }
278  }
279  }
280  }
281  return $success;
282  }
283 
290  public function ‪verifyFileChecksum(CoreRelease $coreRelease)
291  {
292  $version = $coreRelease->getVersion();
293  $success = true;
294  if ($this->‪checkCoreFilesAvailable($version)) {
295  $this->messages->enqueue(new FlashMessage(
296  '',
297  'Verifying existing TYPO3 CMS core checksum is not possible',
298  ContextualFeedbackSeverity::WARNING
299  ));
300  } else {
301  $fileLocation = $this->‪getDownloadTarGzTargetPath($version);
302  $expectedChecksum = $coreRelease->getChecksum();
303  if (!file_exists($fileLocation)) {
304  $success = false;
305  $this->messages->enqueue(new FlashMessage(
306  '',
307  'Downloaded TYPO3 CMS core not found',
308  ContextualFeedbackSeverity::ERROR
309  ));
310  } else {
311  $actualChecksum = sha1_file($fileLocation);
312  if ($actualChecksum !== $expectedChecksum) {
313  $success = false;
314  $this->messages->enqueue(new FlashMessage(
315  'The official TYPO3 CMS version system on https://get.typo3.org expects a sha1 checksum of '
316  . $expectedChecksum . ' from the content of the downloaded new TYPO3 CMS core version ' . $version . '.'
317  . ' The actual checksum is ' . $actualChecksum . '. The update is stopped. This may be a'
318  . ' failed download, an attack, or an issue with the typo3.org infrastructure.',
319  'New TYPO3 CMS core checksum mismatch',
320  ContextualFeedbackSeverity::ERROR
321  ));
322  } else {
323  $this->messages->enqueue(new FlashMessage(
324  '',
325  'Checksum verified'
326  ));
327  }
328  }
329  }
330  return $success;
331  }
332 
339  public function ‪unpackVersion(CoreRelease $coreRelease)
340  {
341  $version = $coreRelease->getVersion();
342  $success = true;
343  if ($this->‪checkCoreFilesAvailable($version)) {
344  $this->messages->enqueue(new FlashMessage(
345  '',
346  'Unpacking TYPO3 CMS core files skipped',
347  ContextualFeedbackSeverity::NOTICE
348  ));
349  } else {
350  $fileLocation = $this->downloadTargetPath . $version . '.tar.gz';
351  if (!@is_file($fileLocation)) {
352  $success = false;
353  $this->messages->enqueue(new FlashMessage(
354  '',
355  'Downloaded TYPO3 CMS core not found',
356  ContextualFeedbackSeverity::ERROR
357  ));
358  } elseif (@file_exists($this->downloadTargetPath . 'typo3_src-' . $version)) {
359  $success = false;
360  $this->messages->enqueue(new FlashMessage(
361  '',
362  'Unpacked TYPO3 CMS core exists in download location: ' . ‪PathUtility::stripPathSitePrefix($this->downloadTargetPath),
363  ContextualFeedbackSeverity::ERROR
364  ));
365  } else {
366  $unpackCommand = 'tar xf ' . escapeshellarg($fileLocation) . ' -C ' . escapeshellarg($this->downloadTargetPath) . ' 2>&1';
367  exec($unpackCommand, ‪$output, $errorCode);
368  if ($errorCode) {
369  $success = false;
370  $this->messages->enqueue(new FlashMessage(
371  '',
372  'Unpacking TYPO3 CMS core not successful',
373  ContextualFeedbackSeverity::ERROR
374  ));
375  } else {
376  $removePackedFileResult = unlink($fileLocation);
377  if (!$removePackedFileResult) {
378  $success = false;
379  $this->messages->enqueue(new FlashMessage(
380  '',
381  'Removing packed TYPO3 CMS core not successful',
382  ContextualFeedbackSeverity::ERROR
383  ));
384  } else {
385  $this->messages->enqueue(new FlashMessage(
386  '',
387  'Unpacking TYPO3 CMS core successful'
388  ));
389  }
390  }
391  }
392  }
393  return $success;
394  }
395 
402  public function ‪moveVersion(CoreRelease $coreRelease)
403  {
404  $version = $coreRelease->getVersion();
405  $success = true;
406  if ($this->‪checkCoreFilesAvailable($version)) {
407  $this->messages->enqueue(new FlashMessage(
408  '',
409  'Moving TYPO3 CMS core files skipped',
410  ContextualFeedbackSeverity::NOTICE
411  ));
412  } else {
413  $downloadedCoreLocation = $this->downloadTargetPath . 'typo3_src-' . $version;
414  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
415 
416  if (!@is_dir($downloadedCoreLocation)) {
417  $success = false;
418  $this->messages->enqueue(new FlashMessage(
419  '',
420  'Unpacked TYPO3 CMS core not found',
421  ContextualFeedbackSeverity::ERROR
422  ));
423  } else {
424  $moveResult = rename($downloadedCoreLocation, $newCoreLocation);
425  if (!$moveResult) {
426  $success = false;
427  $this->messages->enqueue(new FlashMessage(
428  '',
429  'Moving TYPO3 CMS core to ' . $newCoreLocation . ' failed',
430  ContextualFeedbackSeverity::ERROR
431  ));
432  } else {
433  $this->messages->enqueue(new FlashMessage(
434  '',
435  'Moved TYPO3 CMS core to final location'
436  ));
437  }
438  }
439  }
440  return $success;
441  }
442 
449  public function ‪activateVersion(CoreRelease $coreRelease)
450  {
451  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $coreRelease->getVersion();
452  $success = true;
453  if (!is_dir($newCoreLocation)) {
454  $success = false;
455  $this->messages->enqueue(new FlashMessage(
456  '',
457  'New TYPO3 CMS core not found',
458  ContextualFeedbackSeverity::ERROR
459  ));
460  } elseif (!is_link($this->symlinkToCoreFiles)) {
461  $success = false;
462  $this->messages->enqueue(new FlashMessage(
463  '',
464  'TYPO3 CMS core source directory (typo3_src) is not a link',
465  ContextualFeedbackSeverity::ERROR
466  ));
467  } else {
468  $isCurrentCoreSymlinkAbsolute = ‪PathUtility::isAbsolutePath((string)readlink($this->symlinkToCoreFiles));
469  $unlinkResult = unlink($this->symlinkToCoreFiles);
470  if (!$unlinkResult) {
471  $success = false;
472  $this->messages->enqueue(new FlashMessage(
473  '',
474  'Removing old symlink failed',
475  ContextualFeedbackSeverity::ERROR
476  ));
477  } else {
478  if (!$isCurrentCoreSymlinkAbsolute) {
479  $newCoreLocation = $this->‪getRelativePath($newCoreLocation);
480  }
481  $symlinkResult = symlink($newCoreLocation, $this->symlinkToCoreFiles);
482  if ($symlinkResult) {
483  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
484  } else {
485  $success = false;
486  $this->messages->enqueue(new FlashMessage(
487  '',
488  'Linking new TYPO3 CMS core failed',
489  ContextualFeedbackSeverity::ERROR
490  ));
491  }
492  }
493  }
494  return $success;
495  }
496 
503  protected function ‪getDownloadTarGzTargetPath($version)
504  {
505  return $this->downloadTargetPath . $version . '.tar.gz';
506  }
507 
514  protected function ‪getRelativePath($absolutePath)
515  {
516  $sourcePath = explode(DIRECTORY_SEPARATOR, ‪Environment::getPublicPath());
517  $targetPath = explode(DIRECTORY_SEPARATOR, rtrim($absolutePath, DIRECTORY_SEPARATOR));
518  while (count($sourcePath) && count($targetPath) && $sourcePath[0] === $targetPath[0]) {
519  array_shift($sourcePath);
520  array_shift($targetPath);
521  }
522  return str_pad('', count($sourcePath) * 3, '..' . DIRECTORY_SEPARATOR) . implode(DIRECTORY_SEPARATOR, $targetPath);
523  }
524 
532  protected function ‪checkCoreFilesAvailable($version)
533  {
534  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
535  return @is_dir($newCoreLocation);
536  }
537 }
‪TYPO3\CMS\Install\Service\CoreUpdateService\activateVersion
‪bool activateVersion(CoreRelease $coreRelease)
Definition: CoreUpdateService.php:444
‪TYPO3\CMS\Install\Service\CoreUpdateService\getRelativePath
‪string getRelativePath($absolutePath)
Definition: CoreUpdateService.php:509
‪TYPO3\CMS\Install\Service\CoreUpdateService\unpackVersion
‪bool unpackVersion(CoreRelease $coreRelease)
Definition: CoreUpdateService.php:334
‪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:285
‪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\__construct
‪__construct(CoreVersionService $coreVersionService)
Definition: CoreUpdateService.php:69
‪TYPO3\CMS\Install\Service\CoreUpdateService\$downloadBaseUri
‪string $downloadBaseUri
Definition: CoreUpdateService.php:67
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Install\Service\CoreUpdateService\$symlinkToCoreFiles
‪string $symlinkToCoreFiles
Definition: CoreUpdateService.php:61
‪TYPO3\CMS\Install\Service\CoreUpdateService\downloadVersion
‪bool downloadVersion(CoreRelease $coreRelease)
Definition: CoreUpdateService.php:228
‪TYPO3\CMS\Install\FolderStructure\DefaultFactory
Definition: DefaultFactory.php:26
‪TYPO3\CMS\Install\Service\CoreUpdateService\checkCoreFilesAvailable
‪bool checkCoreFilesAvailable($version)
Definition: CoreUpdateService.php:527
‪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:128
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Install\Service\CoreUpdateService\isCoreUpdateEnabled
‪bool isCoreUpdateEnabled()
Definition: CoreUpdateService.php:83
‪TYPO3\CMS\Install\Service\CoreUpdateService\discoverCurrentCoreSymlink
‪string discoverCurrentCoreSymlink()
Definition: CoreUpdateService.php:94
‪TYPO3\CMS\Install\Service\CoreUpdateService\$messages
‪FlashMessageQueue $messages
Definition: CoreUpdateService.php:49
‪TYPO3\CMS\Core\Utility\GeneralUtility\getUrl
‪static string false getUrl(string $url)
Definition: GeneralUtility.php:1427
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep($directory)
Definition: GeneralUtility.php:1638
‪TYPO3\CMS\Install\Service\CoreUpdateService\getMessages
‪getMessages()
Definition: CoreUpdateService.php:116
‪TYPO3\CMS\Install\CoreVersion\CoreRelease\getChecksum
‪getChecksum()
Definition: CoreRelease.php:60
‪TYPO3\CMS\Core\Service\OpcodeCacheService
Definition: OpcodeCacheService.php:27
‪TYPO3\CMS\Install\Service\CoreUpdateService\moveVersion
‪bool moveVersion(CoreRelease $coreRelease)
Definition: CoreUpdateService.php:397
‪$output
‪$output
Definition: annotationChecker.php:119
‪TYPO3\CMS\Install\Service\CoreUpdateService\getDownloadTarGzTargetPath
‪string getDownloadTarGzTargetPath($version)
Definition: CoreUpdateService.php:498
‪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:105
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Install\CoreVersion\CoreRelease\getVersion
‪getVersion()
Definition: CoreRelease.php:45
‪TYPO3\CMS\Core\Messaging\FlashMessageQueue
Definition: FlashMessageQueue.php:30
‪TYPO3\CMS\Install\Service
Definition: ClearCacheService.php:16
‪TYPO3\CMS\Install\Service\CoreUpdateService\$downloadTargetPath
‪string $downloadTargetPath
Definition: CoreUpdateService.php:55
‪TYPO3\CMS\Install\Service\CoreUpdateService\$coreVersionService
‪CoreVersionService $coreVersionService
Definition: CoreUpdateService.php:45
‪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