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