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