TYPO3 CMS  TYPO3_7-6
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 {
46  protected $objectManager;
47 
52 
56  protected $messages = [];
57 
64 
71 
77  protected $downloadBaseUri;
78 
82  public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
83  {
84  $this->objectManager = $objectManager;
85  }
86 
91  {
92  $this->coreVersionService = $coreVersionService;
93  }
94 
98  public function initializeObject()
99  {
100  $this->setDownloadTargetPath(PATH_site . 'typo3temp/core-update/');
101  $this->symlinkToCoreFiles = $this->discoverCurrentCoreSymlink();
102  $this->downloadBaseUri = $this->coreVersionService->getDownloadBaseUri();
103  }
104 
110  public function isCoreUpdateEnabled()
111  {
112  $coreUpdateDisabled = getenv('TYPO3_DISABLE_CORE_UPDATER') ?: (getenv('REDIRECT_TYPO3_DISABLE_CORE_UPDATER') ?: false);
113  return !Bootstrap::usesComposerClassLoading() && !$coreUpdateDisabled;
114  }
115 
121  protected function discoverCurrentCoreSymlink()
122  {
123  return PATH_site . 'typo3_src';
124  }
125 
133  {
134  if (!is_dir($downloadTargetPath)) {
136  }
137  $this->downloadTargetPath = $downloadTargetPath;
138  }
139 
145  public function getMessages()
146  {
147  return $this->messages;
148  }
149 
155  public function updateVersionMatrix()
156  {
157  $success = true;
158  try {
159  $this->coreVersionService->updateVersionMatrix();
160  } catch (RemoteFetchException $e) {
161  $success = false;
163  $message = $this->objectManager->get(ErrorStatus::class);
164  $message->setTitle('Version matrix could not be fetched from get.typo3.org');
165  $message->setMessage(
166  'Current version specification could not be fetched from http://get.typo3.org/json.'
167  . ' This is probably a network issue, please fix it.'
168  );
169  $this->messages = [$message];
170  }
171  return $success;
172  }
173 
181  public function checkPreConditions($version)
182  {
183  $success = true;
184  $messages = [];
185 
187  $statusUtility = $this->objectManager->get(StatusUtility::class);
188 
189  // Folder structure test: Update can be done only if folder structure returns no errors
191  $folderStructureFacade = $this->objectManager->get(DefaultFactory::class)->getStructure();
192  $folderStructureErrors = $statusUtility->filterBySeverity($folderStructureFacade->getStatus(), 'error');
193  $folderStructureWarnings = $statusUtility->filterBySeverity($folderStructureFacade->getStatus(), 'warning');
194  if (!empty($folderStructureErrors) || !empty($folderStructureWarnings)) {
195  $success = false;
197  $message = $this->objectManager->get(ErrorStatus::class);
198  $message->setTitle('Automatic TYPO3 CMS core update not possible: Folder structure has errors or warnings');
199  $message->setMessage(
200  'To perform an update, the folder structure of this TYPO3 CMS instance must'
201  . ' stick to the conventions, or the update process could lead to unexpected'
202  . ' results and may be hazardous to your system'
203  );
204  $messages[] = $message;
205  }
206 
207  // No core update on windows
208  if (TYPO3_OS === 'WIN') {
209  $success = false;
211  $message = $this->objectManager->get(ErrorStatus::class);
212  $message->setTitle('Automatic TYPO3 CMS core update not possible: Update not supported on Windows OS');
213  $messages[] = $message;
214  }
215 
216  if ($success) {
217  // Explicit write check to document root
218  $file = PATH_site . StringUtility::getUniqueId('install-core-update-test-');
219  $result = @touch($file);
220  if (!$result) {
221  $success = false;
223  $message = $this->objectManager->get(ErrorStatus::class);
224  $message->setTitle('Automatic TYPO3 CMS core update not possible: No write access to document root');
225  $message->setMessage('Could not write a file in path "' . PATH_site . '"!');
226  $messages[] = $message;
227  } else {
228  unlink($file);
229  }
230 
231  if (!$this->checkCoreFilesAvailable($version)) {
232  // Explicit write check to upper directory of current core location
233  $coreLocation = @realpath($this->symlinkToCoreFiles . '/../');
234  $file = $coreLocation . '/' . StringUtility::getUniqueId('install-core-update-test-');
235  $result = @touch($file);
236  if (!$result) {
237  $success = false;
239  $message = $this->objectManager->get(ErrorStatus::class);
240  $message->setTitle('Automatic TYPO3 CMS core update not possible: No write access to TYPO3 CMS core location');
241  $message->setMessage(
242  'New TYPO3 CMS core should be installed in "' . $coreLocation . '", but this directory is not writable!'
243  );
244  $messages[] = $message;
245  } else {
246  unlink($file);
247  }
248  }
249  }
250 
251  if ($success && !$this->coreVersionService->isInstalledVersionAReleasedVersion()) {
252  $success = false;
254  $message = $this->objectManager->get(ErrorStatus::class);
255  $message->setTitle('Automatic TYPO3 CMS core update not possible: You are running a development version of TYPO3');
256  $message->setMessage(
257  'Your current version is specified as ' . $this->coreVersionService->getInstalledVersion() . '.'
258  . ' This is a development version and can not be updated automatically. If this is a "git"'
259  . ' checkout, please update using git directly.'
260  );
261  $messages[] = $message;
262  }
263 
264  $this->messages = $messages;
265  return $success;
266  }
267 
274  public function downloadVersion($version)
275  {
276  $messages = [];
277  $success = true;
278 
279  if ($this->checkCoreFilesAvailable($version)) {
281  $message = $this->objectManager->get(NoticeStatus::class);
282  $message->setTitle('Skipped download of TYPO3 CMS core. A core source directory already exists in destination path. Using this instead.');
283  $messages[] = $message;
284  } else {
285  $downloadUri = $this->downloadBaseUri . $version;
286  $fileLocation = $this->getDownloadTarGzTargetPath($version);
287 
288  if (@file_exists($fileLocation)) {
289  $success = false;
291  $message = $this->objectManager->get(ErrorStatus::class);
292  $message->setTitle('TYPO3 CMS core download exists in download location: ' . PathUtility::stripPathSitePrefix($this->downloadTargetPath));
293  $messages[] = $message;
294  } else {
295  $fileContent = GeneralUtility::getUrl($downloadUri);
296  if (!$fileContent) {
297  $success = false;
299  $message = $this->objectManager->get(ErrorStatus::class);
300  $message->setTitle('Download not successful');
301  $messages[] = $message;
302  } else {
303  $fileStoreResult = file_put_contents($fileLocation, $fileContent);
304  if (!$fileStoreResult) {
305  $success = false;
307  $message = $this->objectManager->get(ErrorStatus::class);
308  $message->setTitle('Unable to store download content');
309  $messages[] = $message;
310  } else {
311  $message = $this->objectManager->get(OkStatus::class);
312  $message->setTitle('TYPO3 CMS core download finished');
313  $messages[] = $message;
314  }
315  }
316  }
317  }
318  $this->messages = $messages;
319  return $success;
320  }
321 
328  public function verifyFileChecksum($version)
329  {
330  $messages = [];
331  $success = true;
332 
333  if ($this->checkCoreFilesAvailable($version)) {
335  $message = $this->objectManager->get(WarningStatus::class);
336  $message->setTitle('Verifying existing TYPO3 CMS core checksum is not possible');
337  $messages[] = $message;
338  } else {
339  $fileLocation = $this->getDownloadTarGzTargetPath($version);
340  $expectedChecksum = $this->coreVersionService->getTarGzSha1OfVersion($version);
341 
342  if (!file_exists($fileLocation)) {
343  $success = false;
345  $message = $this->objectManager->get(ErrorStatus::class);
346  $message->setTitle('Downloaded TYPO3 CMS core not found');
347  $messages[] = $message;
348  } else {
349  $actualChecksum = sha1_file($fileLocation);
350  if ($actualChecksum !== $expectedChecksum) {
351  $success = false;
353  $message = $this->objectManager->get(ErrorStatus::class);
354  $message->setTitle('New TYPO3 CMS core checksum mismatch');
355  $message->setMessage(
356  'The official TYPO3 CMS version system on https://get.typo3.org expects a sha1 checksum of '
357  . $expectedChecksum . ' from the content of the downloaded new TYPO3 CMS core version ' . $version . '.'
358  . ' The actual checksum is ' . $actualChecksum . '. The update is stopped. This may be a'
359  . ' failed download, an attack, or an issue with the typo3.org infrastructure.'
360  );
361  $messages[] = $message;
362  } else {
363  $message = $this->objectManager->get(OkStatus::class);
364  $message->setTitle('Checksum verified');
365  $messages[] = $message;
366  }
367  }
368  }
369  $this->messages = $messages;
370  return $success;
371  }
372 
379  public function unpackVersion($version)
380  {
381  $messages = [];
382  $success = true;
383 
384  if ($this->checkCoreFilesAvailable($version)) {
386  $message = $this->objectManager->get(NoticeStatus::class);
387  $message->setTitle('Unpacking TYPO3 CMS core files skipped');
388  $messages[] = $message;
389  } else {
390  $fileLocation = $this->downloadTargetPath . $version . '.tar.gz';
391 
392  if (!@is_file($fileLocation)) {
393  $success = false;
395  $message = $this->objectManager->get(ErrorStatus::class);
396  $message->setTitle('Downloaded TYPO3 CMS core not found');
397  $messages[] = $message;
398  } elseif (@file_exists($this->downloadTargetPath . 'typo3_src-' . $version)) {
399  $success = false;
401  $message = $this->objectManager->get(ErrorStatus::class);
402  $message->setTitle('Unpacked TYPO3 CMS core exists in download location: ' . PathUtility::stripPathSitePrefix($this->downloadTargetPath));
403  $messages[] = $message;
404  } else {
405  $unpackCommand = 'tar xf ' . escapeshellarg($fileLocation) . ' -C ' . escapeshellarg($this->downloadTargetPath) . ' 2>&1';
406  exec($unpackCommand, $output, $errorCode);
407  if ($errorCode) {
408  $success = false;
410  $message = $this->objectManager->get(ErrorStatus::class);
411  $message->setTitle('Unpacking TYPO3 CMS core not successful');
412  $messages[] = $message;
413  } else {
414  $removePackedFileResult = unlink($fileLocation);
415  if (!$removePackedFileResult) {
416  $success = false;
418  $message = $this->objectManager->get(ErrorStatus::class);
419  $message->setTitle('Removing packed TYPO3 CMS core not successful');
420  $messages[] = $message;
421  } else {
422  $message = $this->objectManager->get(OkStatus::class);
423  $message->setTitle('Unpacking TYPO3 CMS core successful');
424  $messages[] = $message;
425  }
426  }
427  }
428  }
429  $this->messages = $messages;
430  return $success;
431  }
432 
439  public function moveVersion($version)
440  {
441  $messages = [];
442  $success = true;
443 
444  if ($this->checkCoreFilesAvailable($version)) {
446  $message = $this->objectManager->get(NoticeStatus::class);
447  $message->setTitle('Moving TYPO3 CMS core files skipped');
448  $messages[] = $message;
449  } else {
450  $downloadedCoreLocation = $this->downloadTargetPath . 'typo3_src-' . $version;
451  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
452 
453  if (!@is_dir($downloadedCoreLocation)) {
454  $success = false;
456  $message = $this->objectManager->get(ErrorStatus::class);
457  $message->setTitle('Unpacked TYPO3 CMS core not found');
458  $messages[] = $message;
459  } else {
460  $moveResult = rename($downloadedCoreLocation, $newCoreLocation);
461  if (!$moveResult) {
462  $success = false;
464  $message = $this->objectManager->get(ErrorStatus::class);
465  $message->setTitle('Moving TYPO3 CMS core to ' . $newCoreLocation . ' failed');
466  $messages[] = $message;
467  } else {
468  $message = $this->objectManager->get(OkStatus::class);
469  $message->setTitle('Moved TYPO3 CMS core to final location');
470  $messages[] = $message;
471  }
472  }
473  }
474 
475  $this->messages = $messages;
476  return $success;
477  }
478 
485  public function activateVersion($version)
486  {
487  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
488 
489  $messages = [];
490  $success = true;
491 
492  if (!is_dir($newCoreLocation)) {
493  $success = false;
495  $message = $this->objectManager->get(ErrorStatus::class);
496  $message->setTitle('New TYPO3 CMS core not found');
497  $messages[] = $message;
498  } elseif (!is_link($this->symlinkToCoreFiles)) {
499  $success = false;
501  $message = $this->objectManager->get(ErrorStatus::class);
502  $message->setTitle('TYPO3 CMS core source directory (typo3_src) is not a link');
503  $messages[] = $message;
504  } else {
505  $isCurrentCoreSymlinkAbsolute = PathUtility::isAbsolutePath(readlink($this->symlinkToCoreFiles));
506  $unlinkResult = unlink($this->symlinkToCoreFiles);
507  if (!$unlinkResult) {
508  $success = false;
510  $message = $this->objectManager->get(ErrorStatus::class);
511  $message->setTitle('Removing old symlink failed');
512  $messages[] = $message;
513  } else {
514  if (!$isCurrentCoreSymlinkAbsolute) {
515  $newCoreLocation = $this->getRelativePath($newCoreLocation);
516  }
517  $symlinkResult = symlink($newCoreLocation, $this->symlinkToCoreFiles);
518  if ($symlinkResult) {
519  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
520  } else {
521  $success = false;
523  $message = $this->objectManager->get(ErrorStatus::class);
524  $message->setTitle('Linking new TYPO3 CMS core failed');
525  $messages[] = $message;
526  }
527  }
528  }
529 
530  $this->messages = $messages;
531  return $success;
532  }
533 
540  protected function getDownloadTarGzTargetPath($version)
541  {
542  return $this->downloadTargetPath . $version . '.tar.gz';
543  }
544 
551  protected function getRelativePath($absolutePath)
552  {
553  $sourcePath = explode(DIRECTORY_SEPARATOR, rtrim(PATH_site, DIRECTORY_SEPARATOR));
554  $targetPath = explode(DIRECTORY_SEPARATOR, rtrim($absolutePath, DIRECTORY_SEPARATOR));
555  while (count($sourcePath) && count($targetPath) && $sourcePath[0] === $targetPath[0]) {
556  array_shift($sourcePath);
557  array_shift($targetPath);
558  }
559  return str_pad('', count($sourcePath) * 3, '..' . DIRECTORY_SEPARATOR) . implode(DIRECTORY_SEPARATOR, $targetPath);
560  }
561 
569  protected function checkCoreFilesAvailable($version)
570  {
571  $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version;
572  return @is_dir($newCoreLocation);
573  }
574 }
static mkdir_deep($directory, $deepDirectory='')
injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
injectCoreVersionService(\TYPO3\CMS\Install\Service\CoreVersionService $coreVersionService)
static getUrl($url, $includeHeader=0, $requestHeaders=false, &$report=null)