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