[BUGFIX] TceformsUpdateWizard shows up too often
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Service / CoreUpdateService.php
1 <?php
2 namespace TYPO3\CMS\Install\Service;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2013 Christian Kuhn <lolli@schwarzbu.ch>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * A copy is found in the text file GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31
32 /**
33 * Core update service.
34 * This service handles core updates, all the nasty details are encapsulated
35 * here. The single public methods 'depend' on each other, for example a new
36 * core has to be downloaded before it can be unpacked.
37 *
38 * Each method returns only TRUE of FALSE indicating if it was successful or
39 * not. Detailed information can be fetched with getMessages() and will return
40 * a list of status messages of the previous operation.
41 */
42 class CoreUpdateService {
43
44 /**
45 * @var \TYPO3\CMS\Extbase\Object\ObjectManager
46 * @inject
47 */
48 protected $objectManager;
49
50 /**
51 * @var \TYPO3\CMS\Install\Service\CoreVersionService
52 * @inject
53 */
54 protected $coreVersionService;
55
56 /**
57 * @var array<\TYPO3\CMS\Install\Status\StatusInterface>
58 */
59 protected $messages = array();
60
61 /**
62 * Absolute path to download location
63 *
64 * @var string
65 */
66 protected $downloadTargetPath;
67
68 /**
69 * Absolute path to the current core files
70 *
71 * @var string
72 */
73 protected $currentCoreLocation;
74
75 /**
76 * Base URI for TYPO3 downloads
77 *
78 * @var string
79 */
80 protected $downloadBaseUri;
81
82 /**
83 * Initialize update paths
84 */
85 public function initializeObject() {
86 $this->setDownloadTargetPath(PATH_site . 'typo3temp/core-update/');
87 $this->currentCoreLocation = $this->discoverCurrentCoreLocation();
88 $this->downloadBaseUri = $this->coreVersionService->getDownloadBaseUri();
89 }
90
91 /**
92 * Check if this installation wants to enable the core updater
93 *
94 * @return boolean
95 */
96 public function isCoreUpdateEnabled() {
97 return (getenv('TYPO3_DISABLE_CORE_UPDATER') !== '1');
98 }
99
100 /**
101 * In future implementations we might implement some smarter logic here
102 *
103 * @return string
104 */
105 protected function discoverCurrentCoreLocation() {
106 return PATH_site . 'typo3_src';
107 }
108
109 /**
110 * Create download location in case the folder does not exist
111 * @todo move this to folder structure
112 *
113 * @param string $downloadTargetPath
114 */
115 protected function setDownloadTargetPath($downloadTargetPath) {
116 if (!is_dir($downloadTargetPath)) {
117 GeneralUtility::mkdir_deep($downloadTargetPath);
118 }
119 $this->downloadTargetPath = $downloadTargetPath;
120 }
121
122 /**
123 * Get messages of previous method call
124 *
125 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
126 */
127 public function getMessages() {
128 return $this->messages;
129 }
130
131 /**
132 * Wrapper method for CoreVersionService
133 *
134 * @return boolean TRUE on success
135 */
136 public function updateVersionMatrix() {
137 $success = TRUE;
138 try {
139 $this->coreVersionService->updateVersionMatrix();
140 } catch (\TYPO3\CMS\Install\Service\Exception\RemoteFetchException $e) {
141 $success = FALSE;
142 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
143 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
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 = array($message);
150 }
151 return $success;
152 }
153
154 /**
155 * Check if an update is possible at all
156 *
157 * @return boolean TRUE on success
158 */
159 public function checkPreConditions() {
160 $success = TRUE;
161 $messages = array();
162
163 /** @var \TYPO3\CMS\Install\Status\StatusUtility $statusUtility */
164 $statusUtility = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\StatusUtility');
165
166 // Folder structure test: Update can be done only if folder structure returns no errors
167 /** @var $folderStructureFacade \TYPO3\CMS\Install\FolderStructure\StructureFacade */
168 $folderStructureFacade = $this->objectManager->get('TYPO3\\CMS\\Install\\FolderStructure\\DefaultFactory')->getStructure();
169 $folderStructureErrors = $statusUtility->filterBySeverity($folderStructureFacade->getStatus(), 'error');
170 $folderStructureWarnings = $statusUtility->filterBySeverity($folderStructureFacade->getStatus(), 'warning');
171 if (count($folderStructureErrors) > 0 || count($folderStructureWarnings) > 0) {
172 $success = FALSE;
173 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
174 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
175 $message->setTitle('Automatic core update not possible: Folder structure has errors or warnings');
176 $message->setMessage(
177 'To perform an update, the folder structure of this TYPO3 CMS instance must'
178 . ' stick to the conventions, or the update process could lead to unexpected'
179 . ' results and may be hazardous to your system'
180 );
181 $messages[] = $message;
182 }
183
184 // No core update on windows
185 if (TYPO3_OS === 'WIN') {
186 $success = FALSE;
187 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
188 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
189 $message->setTitle('Automatic core update not possible: Update not supported on Windows OS');
190 $messages[] = $message;
191 }
192
193 if ($success) {
194 // Explicit write check to document root
195 $file = PATH_site . uniqid('install-core-update-test-');
196 $result = @touch($file);
197 if (!$result) {
198 $success = FALSE;
199 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
200 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
201 $message->setTitle('Automatic core update not possible: No write access to document root');
202 $message->setMessage('Could not write a file in path "' . PATH_site . '"!');
203 $messages[] = $message;
204 } else {
205 unlink($file);
206 }
207
208 // Explicit write check to upper directory of current core location
209 $coreLocation = @realPath($this->currentCoreLocation . '/../');
210 $file = $coreLocation . '/' . uniqid('install-core-update-test-');
211 $result = @touch($file);
212 if (!$result) {
213 $success = FALSE;
214 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
215 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
216 $message->setTitle('Automatic core update not possible: No write access to core location');
217 $message->setMessage(
218 'New core should be installed in "' . $coreLocation . '", but this directory is not writable!'
219 );
220 $messages[] = $message;
221 } else {
222 unlink($file);
223 }
224 }
225
226 if ($success && !$this->coreVersionService->isInstalledVersionAReleasedVersion()) {
227 $success = FALSE;
228 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
229 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
230 $message->setTitle('Automatic core update not possible: You are running a development version of TYPO3');
231 $message->setMessage(
232 'Your current version is specified as ' . $this->coreVersionService->getInstalledVersion() . '.'
233 . ' This is a development version and can not be updated automatically. If this is a "git"'
234 . ' checkout, please update using git directly.'
235 );
236 $messages[] = $message;
237 }
238
239 $this->messages = $messages;
240 return $success;
241 }
242
243 /**
244 * Download the specified version
245 *
246 * @param string $version A version to download
247 * @return boolean TRUE on success
248 */
249 public function downloadVersion($version) {
250 $downloadUri = $this->downloadBaseUri . $version;
251 $fileLocation = $this->getDownloadTarGzTargetPath($version);
252
253 $messages = array();
254 $success = TRUE;
255
256 if (@file_exists($fileLocation)) {
257 $success = FALSE;
258 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
259 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
260 $message->setTitle('Core download exists in download location: ' . \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($this->downloadTargetPath));
261 $messages[] = $message;
262 } else {
263 $fileContent = GeneralUtility::getUrl($downloadUri);
264 if (!$fileContent) {
265 $success = FALSE;
266 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
267 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
268 $message->setTitle('Download not successful');
269 $messages[] = $message;
270 } else {
271 $fileStoreResult = file_put_contents($fileLocation, $fileContent);
272 if (!$fileStoreResult) {
273 $success = FALSE;
274 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
275 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
276 $message->setTitle('Unable to store download content');
277 $messages[] = $message;
278 }
279 }
280 }
281 $this->messages = $messages;
282 return $success;
283 }
284
285 /**
286 * Verify checksum of downloaded version
287 *
288 * @param string $version A downloaded version to check
289 * @return boolean TRUE on success
290 */
291 public function verifyFileChecksum($version) {
292 $fileLocation = $this->getDownloadTarGzTargetPath($version);
293 $expectedChecksum = $this->coreVersionService->getTarGzSha1OfVersion($version);
294
295 $messages = array();
296 $success = TRUE;
297
298 if (!file_exists($fileLocation)) {
299 $success = FALSE;
300 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
301 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
302 $message->setTitle('Downloaded core not found');
303 $messages[] = $message;
304 } else {
305 $actualChecksum = sha1_file($fileLocation);
306 if ($actualChecksum !== $expectedChecksum) {
307 $success = FALSE;
308 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
309 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
310 $message->setTitle('New core checksum mismatch');
311 $message->setMessage(
312 'The official TYPO3 CMS version system on https://get.typo3.org expects a sha1 checksum of '
313 . $expectedChecksum . ' from the content of the downloaded new core version ' . $version . '.'
314 . ' The actual checksum is ' . $actualChecksum . '. The update is stopped. This may be a'
315 . ' failed download, an attack, or an issue with the typo3.org infrastructure.'
316 );
317 $messages[] = $message;
318 }
319 }
320 $this->messages = $messages;
321 return $success;
322 }
323
324 /**
325 * Unpack a downloaded core
326 *
327 * @param string $version A version to unpack
328 * @return boolean TRUE on success
329 */
330 public function unpackVersion($version) {
331 $fileLocation = $this->downloadTargetPath . $version . '.tar.gz';
332
333 $messages = array();
334 $success = TRUE;
335
336 if (!@is_file($fileLocation)) {
337 $success = FALSE;
338 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
339 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
340 $message->setTitle('Downloaded core not found');
341 $messages[] = $message;
342 } elseif (@file_exists($this->downloadTargetPath . 'typo3_src-' . $version)) {
343 $success = FALSE;
344 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
345 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
346 $message->setTitle('Unpacked core exists in download location: ' . \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($this->downloadTargetPath));
347 $messages[] = $message;
348 } else {
349 $unpackCommand = 'tar xf ' . escapeshellarg($fileLocation) . ' -C ' . escapeshellarg($this->downloadTargetPath) . ' 2>&1';
350 exec($unpackCommand, $output, $errorCode);
351 if ($errorCode) {
352 $success = FALSE;
353 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
354 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
355 $message->setTitle('Unpacking core not successful');
356 $messages[] = $message;
357 } else {
358 $removePackedFileResult = unlink($fileLocation);
359 if (!$removePackedFileResult) {
360 $success = FALSE;
361 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
362 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
363 $message->setTitle('Removing packed core not successful');
364 $messages[] = $message;
365 }
366 }
367 }
368 $this->messages = $messages;
369 return $success;
370 }
371
372 /**
373 * Move an unpacked core to its final destination
374 *
375 * @param string $version A version to move
376 * @return boolean TRUE on success
377 */
378 public function moveVersion($version) {
379 $downloadedCoreLocation = $this->downloadTargetPath . 'typo3_src-' . $version;
380 $newCoreLocation = @realPath($this->currentCoreLocation . '/../') . '/typo3_src-' . $version;
381
382 $messages = array();
383 $success = TRUE;
384
385 if (!@is_dir($downloadedCoreLocation)) {
386 $success = FALSE;
387 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
388 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
389 $message->setTitle('Unpacked core not found');
390 $messages[] = $message;
391 } elseif (@is_dir($newCoreLocation)) {
392 $success = FALSE;
393 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
394 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
395 $message->setTitle('Another core source directory already exists in path ' . $newCoreLocation);
396 $messages[] = $message;
397 } else {
398 $moveResult = rename($downloadedCoreLocation, $newCoreLocation);
399 if (!$moveResult) {
400 $success = FALSE;
401 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
402 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
403 $message->setTitle('Moving core to ' . $newCoreLocation . ' failed');
404 $messages[] = $message;
405 }
406 }
407
408 $this->messages = $messages;
409 return $success;
410 }
411
412 /**
413 * Activate a core version
414 *
415 * @param string $version A version to activate
416 * @return boolean TRUE on success
417 */
418 public function activateVersion($version) {
419 $newCoreLocation = @realPath($this->currentCoreLocation . '/../') . '/typo3_src-' . $version;
420
421 $messages = array();
422 $success = TRUE;
423
424 if (!is_dir($newCoreLocation)) {
425 $success = FALSE;
426 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
427 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
428 $message->setTitle('New core not found');
429 $messages[] = $message;
430 } elseif (!is_link($this->currentCoreLocation)) {
431 $success = FALSE;
432 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
433 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
434 $message->setTitle('TYPO3 source directory (typo3_src) is not a link');
435 $messages[] = $message;
436 } else {
437 $unlinkResult = unlink($this->currentCoreLocation);
438 if (!$unlinkResult) {
439 $success = FALSE;
440 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
441 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
442 $message->setTitle('Removing old symlink failed');
443 $messages[] = $message;
444 } else {
445 $symlinkResult = symlink($newCoreLocation, $this->currentCoreLocation);
446 if (!$symlinkResult) {
447 $success = FALSE;
448 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
449 $message = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
450 $message->setTitle('Linking new core failed');
451 $messages[] = $message;
452 }
453 }
454 }
455
456 $this->messages = $messages;
457 return $success;
458 }
459
460 /**
461 * Absolute path of downloaded .tar.gz
462 *
463 * @param string $version A version number
464 * @return string
465 */
466 protected function getDownloadTarGzTargetPath($version) {
467 return $this->downloadTargetPath . $version . '.tar.gz';
468 }
469 }