TYPO3 CMS  TYPO3_8-7
CoreUpdateService.php
Go to the documentation of this file.
1 <?php
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 
30 
42 {
47 
51  protected $messages = [];
52 
59 
66 
72  protected $downloadBaseUri;
73 
78  {
79  $this->coreVersionService = $coreVersionService ?: GeneralUtility::makeInstance(CoreVersionService::class);
80  $this->setDownloadTargetPath(PATH_site . 'typo3temp/var/transient/');
81  $this->symlinkToCoreFiles = $this->discoverCurrentCoreSymlink();
82  $this->downloadBaseUri = $this->coreVersionService->getDownloadBaseUri();
83  }
84 
90  public function isCoreUpdateEnabled()
91  {
92  $coreUpdateDisabled = getenv('TYPO3_DISABLE_CORE_UPDATER') ?: (getenv('REDIRECT_TYPO3_DISABLE_CORE_UPDATER') ?: false);
93  return !Bootstrap::usesComposerClassLoading() && !$coreUpdateDisabled;
94  }
95 
101  protected function discoverCurrentCoreSymlink()
102  {
103  return PATH_site . 'typo3_src';
104  }
105 
113  {
114  if (!is_dir($downloadTargetPath)) {
116  }
117  $this->downloadTargetPath = $downloadTargetPath;
118  }
119 
125  public function getMessages()
126  {
127  return $this->messages;
128  }
129 
135  public function updateVersionMatrix()
136  {
137  $success = true;
138  try {
139  $this->coreVersionService->updateVersionMatrix();
140  } catch (RemoteFetchException $e) {
141  $success = false;
143  $message = GeneralUtility::makeInstance(ErrorStatus::class);
144  $message->setTitle('Version matrix could not be fetched from get.typo3.org');
145  $message->setMessage(
146  'Current version specification could not be fetched from http://get.typo3.org/json.'
147  . ' This is probably a network issue, please fix it.'
148  );
149  $this->messages = [$message];
150  }
151  return $success;
152  }
153 
161  public function checkPreConditions($version)
162  {
163  $success = true;
164  $messages = [];
165 
167  $statusUtility = GeneralUtility::makeInstance(StatusUtility::class);
168 
169  // Folder structure test: Update can be done only if folder structure returns no errors
171  $folderStructureFacade = GeneralUtility::makeInstance(DefaultFactory::class)->getStructure();
172  $folderStructureErrors = $statusUtility->filterBySeverity($folderStructureFacade->getStatus(), 'error');
173  $folderStructureWarnings = $statusUtility->filterBySeverity($folderStructureFacade->getStatus(), 'warning');
174  if (!empty($folderStructureErrors) || !empty($folderStructureWarnings) || !is_link(PATH_site . 'typo3_src')) {
175  $success = false;
177  $message = GeneralUtility::makeInstance(ErrorStatus::class);
178  $message->setTitle('Automatic TYPO3 CMS core update not possible: Folder structure has errors or warnings');
179  $message->setMessage(
180  'To perform an update, the folder structure of this TYPO3 CMS instance must'
181  . ' stick to the conventions, or the update process could lead to unexpected'
182  . ' results and may be hazardous to your system'
183  );
184  $messages[] = $message;
185  }
186 
187  // No core update on windows
188  if (TYPO3_OS === 'WIN') {
189  $success = false;
191  $message = GeneralUtility::makeInstance(ErrorStatus::class);
192  $message->setTitle('Automatic TYPO3 CMS core update not possible: Update not supported on Windows OS');
193  $messages[] = $message;
194  }
195 
196  if ($success) {
197  // Explicit write check to document root
198  $file = PATH_site . StringUtility::getUniqueId('install-core-update-test-');
199  $result = @touch($file);
200  if (!$result) {
201  $success = false;
203  $message = GeneralUtility::makeInstance(ErrorStatus::class);
204  $message->setTitle('Automatic TYPO3 CMS core update not possible: No write access to document root');
205  $message->setMessage('Could not write a file in path "' . PATH_site . '"!');
206  $messages[] = $message;
207  } else {
208  unlink($file);
209  }
210 
211  if (!$this->checkCoreFilesAvailable($version)) {
212  // Explicit write check to upper directory of current core location
213  $coreLocation = @realpath($this->symlinkToCoreFiles . '/../');
214  $file = $coreLocation . '/' . StringUtility::getUniqueId('install-core-update-test-');
215  $result = @touch($file);
216  if (!$result) {
217  $success = false;
219  $message = GeneralUtility::makeInstance(ErrorStatus::class);
220  $message->setTitle('Automatic TYPO3 CMS core update not possible: No write access to TYPO3 CMS core location');
221  $message->setMessage(
222  'New TYPO3 CMS core should be installed in "' . $coreLocation . '", but this directory is not writable!'
223  );
224  $messages[] = $message;
225  } else {
226  unlink($file);
227  }
228  }
229  }
230 
231  if ($success && !$this->coreVersionService->isInstalledVersionAReleasedVersion()) {
232  $success = false;
234  $message = GeneralUtility::makeInstance(ErrorStatus::class);
235  $message->setTitle('Automatic TYPO3 CMS core update not possible: You are running a development version of TYPO3');
236  $message->setMessage(
237  'Your current version is specified as ' . $this->coreVersionService->getInstalledVersion() . '.'
238  . ' This is a development version and can not be updated automatically. If this is a "git"'
239  . ' checkout, please update using git directly.'
240  );
241  $messages[] = $message;
242  }
243 
244  $this->messages = $messages;
245  return $success;
246  }
247 
254  public function downloadVersion($version)
255  {
256  $messages = [];
257  $success = true;
258 
259  if ($this->checkCoreFilesAvailable($version)) {
261  $message = GeneralUtility::makeInstance(NoticeStatus::class);
262  $message->setTitle('Skipped download of TYPO3 CMS core. A core source directory already exists in destination path. Using this instead.');
263  $messages[] = $message;
264  } else {
265  $downloadUri = $this->downloadBaseUri . $version;
266  $fileLocation = $this->getDownloadTarGzTargetPath($version);
267 
268  if (@file_exists($fileLocation)) {
269  $success = false;
271  $message = GeneralUtility::makeInstance(ErrorStatus::class);
272  $message->setTitle('TYPO3 CMS core download exists in download location: ' . PathUtility::stripPathSitePrefix($this->downloadTargetPath));
273  $messages[] = $message;
274  } else {
275  $fileContent = GeneralUtility::getUrl($downloadUri);
276  if (!$fileContent) {
277  $success = false;
279  $message = GeneralUtility::makeInstance(ErrorStatus::class);
280  $message->setTitle('Download not successful');
281  $messages[] = $message;
282  } else {
283  $fileStoreResult = file_put_contents($fileLocation, $fileContent);
284  if (!$fileStoreResult) {
285  $success = false;
287  $message = GeneralUtility::makeInstance(ErrorStatus::class);
288  $message->setTitle('Unable to store download content');
289  $messages[] = $message;
290  } else {
291  $message = GeneralUtility::makeInstance(OkStatus::class);
292  $message->setTitle('TYPO3 CMS core download finished');
293  $messages[] = $message;
294  }
295  }
296  }
297  }
298  $this->messages = $messages;
299  return $success;
300  }
301 
308  public function verifyFileChecksum($version)
309  {
310  $messages = [];
311  $success = true;
312 
313  if ($this->checkCoreFilesAvailable($version)) {
315  $message = GeneralUtility::makeInstance(WarningStatus::class);
316  $message->setTitle('Verifying existing TYPO3 CMS core checksum is not possible');
317  $messages[] = $message;
318  } else {
319  $fileLocation = $this->getDownloadTarGzTargetPath($version);
320  $expectedChecksum = $this->coreVersionService->getTarGzSha1OfVersion($version);
321 
322  if (!file_exists($fileLocation)) {
323  $success = false;
325  $message = GeneralUtility::makeInstance(ErrorStatus::class);
326  $message->setTitle('Downloaded TYPO3 CMS core not found');
327  $messages[] = $message;
328  } else {
329  $actualChecksum = sha1_file($fileLocation);
330  if ($actualChecksum !== $expectedChecksum) {
331  $success = false;
333  $message = GeneralUtility::makeInstance(ErrorStatus::class);
334  $message->setTitle('New TYPO3 CMS core checksum mismatch');
335  $message->setMessage(
336  'The official TYPO3 CMS version system on https://get.typo3.org expects a sha1 checksum of '
337  . $expectedChecksum . ' from the content of the downloaded new TYPO3 CMS core version ' . $version . '.'
338  . ' The actual checksum is ' . $actualChecksum . '. The update is stopped. This may be a'
339  . ' failed download, an attack, or an issue with the typo3.org infrastructure.'
340  );
341  $messages[] = $message;
342  } else {
343  $message = GeneralUtility::makeInstance(OkStatus::class);
344  $message->setTitle('Checksum verified');
345  $messages[] = $message;
346  }
347  }
348  }
349  $this->messages = $messages;
350  return $success;
351  }
352 
359  public function unpackVersion($version)
360  {
361  $messages = [];
362  $success = true;
363 
364  if ($this->checkCoreFilesAvailable($version)) {
366  $message = GeneralUtility::makeInstance(NoticeStatus::class);
367  $message->setTitle('Unpacking TYPO3 CMS core files skipped');
368  $messages[] = $message;
369  } else {
370  $fileLocation = $this->downloadTargetPath . $version . '.tar.gz';
371 
372  if (!@is_file($fileLocation)) {
373  $success = false;
375  $message = GeneralUtility::makeInstance(ErrorStatus::class);
376  $message->setTitle('Downloaded TYPO3 CMS core not found');
377  $messages[] = $message;
378  } elseif (@file_exists($this->downloadTargetPath . 'typo3_src-' . $version)) {
379  $success = false;
381  $message = GeneralUtility::makeInstance(ErrorStatus::class);
382  $message->setTitle('Unpacked TYPO3 CMS core exists in download location: ' . PathUtility::stripPathSitePrefix($this->downloadTargetPath));
383  $messages[] = $message;
384  } else {
385  $unpackCommand = 'tar xf ' . escapeshellarg($fileLocation) . ' -C ' . escapeshellarg($this->downloadTargetPath) . ' 2>&1';
386  exec($unpackCommand, $output, $errorCode);
387  if ($errorCode) {
388  $success = false;
390  $message = GeneralUtility::makeInstance(ErrorStatus::class);
391  $message->setTitle('Unpacking TYPO3 CMS core not successful');
392  $messages[] = $message;
393  } else {
394  $removePackedFileResult = unlink($fileLocation);
395  if (!$removePackedFileResult) {
396  $success = false;
398  $message = GeneralUtility::makeInstance(ErrorStatus::class);
399  $message->setTitle('Removing packed TYPO3 CMS core not successful');
400  $messages[] = $message;
401  } else {
402  $message = GeneralUtility::makeInstance(OkStatus::class);
403  $message->setTitle('Unpacking TYPO3 CMS core successful');
404  $messages[] = $message;
405  }
406  }
407  }
408  }
409  $this->messages = $messages;
410  return $success;
411  }
412 
419  public function moveVersion($version)
420  {
421  $messages = [];
422  $success = true;
423 
424  if ($this->checkCoreFilesAvailable($version)) {
426  $message = GeneralUtility::makeInstance(NoticeStatus::class);
427  $message->setTitle('Moving TYPO3 CMS core files skipped');
428  $messages[] = $message;
429  } else {
430  $downloadedCoreLocation = $this->downloadTargetPath . 'typo3_src-' . $version;
431  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
432 
433  if (!@is_dir($downloadedCoreLocation)) {
434  $success = false;
436  $message = GeneralUtility::makeInstance(ErrorStatus::class);
437  $message->setTitle('Unpacked TYPO3 CMS core not found');
438  $messages[] = $message;
439  } else {
440  $moveResult = rename($downloadedCoreLocation, $newCoreLocation);
441  if (!$moveResult) {
442  $success = false;
444  $message = GeneralUtility::makeInstance(ErrorStatus::class);
445  $message->setTitle('Moving TYPO3 CMS core to ' . $newCoreLocation . ' failed');
446  $messages[] = $message;
447  } else {
448  $message = GeneralUtility::makeInstance(OkStatus::class);
449  $message->setTitle('Moved TYPO3 CMS core to final location');
450  $messages[] = $message;
451  }
452  }
453  }
454 
455  $this->messages = $messages;
456  return $success;
457  }
458 
465  public function activateVersion($version)
466  {
467  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
468 
469  $messages = [];
470  $success = true;
471 
472  if (!is_dir($newCoreLocation)) {
473  $success = false;
475  $message = GeneralUtility::makeInstance(ErrorStatus::class);
476  $message->setTitle('New TYPO3 CMS core not found');
477  $messages[] = $message;
478  } elseif (!is_link($this->symlinkToCoreFiles)) {
479  $success = false;
481  $message = GeneralUtility::makeInstance(ErrorStatus::class);
482  $message->setTitle('TYPO3 CMS core source directory (typo3_src) is not a link');
483  $messages[] = $message;
484  } else {
485  $isCurrentCoreSymlinkAbsolute = PathUtility::isAbsolutePath(readlink($this->symlinkToCoreFiles));
486  $unlinkResult = unlink($this->symlinkToCoreFiles);
487  if (!$unlinkResult) {
488  $success = false;
490  $message = GeneralUtility::makeInstance(ErrorStatus::class);
491  $message->setTitle('Removing old symlink failed');
492  $messages[] = $message;
493  } else {
494  if (!$isCurrentCoreSymlinkAbsolute) {
495  $newCoreLocation = $this->getRelativePath($newCoreLocation);
496  }
497  $symlinkResult = symlink($newCoreLocation, $this->symlinkToCoreFiles);
498  if ($symlinkResult) {
499  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
500  } else {
501  $success = false;
503  $message = GeneralUtility::makeInstance(ErrorStatus::class);
504  $message->setTitle('Linking new TYPO3 CMS core failed');
505  $messages[] = $message;
506  }
507  }
508  }
509 
510  $this->messages = $messages;
511  return $success;
512  }
513 
520  protected function getDownloadTarGzTargetPath($version)
521  {
522  return $this->downloadTargetPath . $version . '.tar.gz';
523  }
524 
531  protected function getRelativePath($absolutePath)
532  {
533  $sourcePath = explode(DIRECTORY_SEPARATOR, rtrim(PATH_site, DIRECTORY_SEPARATOR));
534  $targetPath = explode(DIRECTORY_SEPARATOR, rtrim($absolutePath, DIRECTORY_SEPARATOR));
535  while (count($sourcePath) && count($targetPath) && $sourcePath[0] === $targetPath[0]) {
536  array_shift($sourcePath);
537  array_shift($targetPath);
538  }
539  return str_pad('', count($sourcePath) * 3, '..' . DIRECTORY_SEPARATOR) . implode(DIRECTORY_SEPARATOR, $targetPath);
540  }
541 
549  protected function checkCoreFilesAvailable($version)
550  {
551  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
552  return @is_dir($newCoreLocation);
553  }
554 }
static mkdir_deep($directory, $deepDirectory='')
static makeInstance($className,... $constructorArguments)
__construct(CoreVersionService $coreVersionService=null)