Total Complexity | 52 |
Total Lines | 492 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like CoreUpdateService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use CoreUpdateService, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
38 | class CoreUpdateService |
||
39 | { |
||
40 | /** |
||
41 | * @var CoreVersionService |
||
42 | */ |
||
43 | protected $coreVersionService; |
||
44 | |||
45 | /** |
||
46 | * @var FlashMessageQueue |
||
47 | */ |
||
48 | protected $messages; |
||
49 | |||
50 | /** |
||
51 | * Absolute path to download location |
||
52 | * |
||
53 | * @var string |
||
54 | */ |
||
55 | protected $downloadTargetPath; |
||
56 | |||
57 | /** |
||
58 | * Absolute path to the symlink pointing to the currently used TYPO3 core files |
||
59 | * |
||
60 | * @var string |
||
61 | */ |
||
62 | protected $symlinkToCoreFiles; |
||
63 | |||
64 | /** |
||
65 | * Base URI for TYPO3 downloads |
||
66 | * |
||
67 | * @var string |
||
68 | */ |
||
69 | protected $downloadBaseUri; |
||
70 | |||
71 | public function __construct(CoreVersionService $coreVersionService) |
||
78 | } |
||
79 | |||
80 | /** |
||
81 | * Check if this installation wants to enable the core updater |
||
82 | * |
||
83 | * @return bool |
||
84 | */ |
||
85 | public function isCoreUpdateEnabled() |
||
86 | { |
||
87 | $coreUpdateDisabled = getenv('TYPO3_DISABLE_CORE_UPDATER') ?: (getenv('REDIRECT_TYPO3_DISABLE_CORE_UPDATER') ?: false); |
||
88 | return !Environment::isComposerMode() && !$coreUpdateDisabled; |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * In future implementations we might implement some smarter logic here |
||
93 | * |
||
94 | * @return string |
||
95 | */ |
||
96 | protected function discoverCurrentCoreSymlink() |
||
97 | { |
||
98 | return Environment::getPublicPath() . '/typo3_src'; |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * Create download location in case the folder does not exist |
||
103 | * @todo move this to folder structure |
||
104 | * |
||
105 | * @param string $downloadTargetPath |
||
106 | */ |
||
107 | protected function setDownloadTargetPath($downloadTargetPath) |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Get messages of previous method call |
||
117 | * |
||
118 | * @return FlashMessageQueue |
||
119 | */ |
||
120 | public function getMessages(): FlashMessageQueue |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * Check if an update is possible at all |
||
127 | * |
||
128 | * @param string $version The target version number |
||
129 | * @return bool TRUE on success |
||
130 | */ |
||
131 | public function checkPreConditions($version) |
||
132 | { |
||
133 | $success = true; |
||
134 | |||
135 | // Folder structure test: Update can be done only if folder structure returns no errors |
||
136 | $folderStructureFacade = GeneralUtility::makeInstance(DefaultFactory::class)->getStructure(); |
||
137 | $folderStructureMessageQueue = $folderStructureFacade->getStatus(); |
||
138 | $folderStructureErrors = $folderStructureMessageQueue->getAllMessages(FlashMessage::ERROR); |
||
139 | $folderStructureWarnings = $folderStructureMessageQueue->getAllMessages(FlashMessage::WARNING); |
||
140 | if (!empty($folderStructureErrors) || !empty($folderStructureWarnings) || !is_link(Environment::getPublicPath() . '/typo3_src')) { |
||
141 | $success = false; |
||
142 | $this->messages->enqueue(new FlashMessage( |
||
143 | 'To perform an update, the folder structure of this TYPO3 CMS instance must' |
||
144 | . ' stick to the conventions, or the update process could lead to unexpected results' |
||
145 | . ' and may be hazardous to your system. Please check your directory status in the' |
||
146 | . ' “Environment” module under “Directory Status”.', |
||
147 | 'Automatic TYPO3 CMS core update not possible: Folder structure has errors or warnings', |
||
148 | FlashMessage::ERROR |
||
149 | )); |
||
150 | } |
||
151 | |||
152 | // No core update on windows |
||
153 | if (Environment::isWindows()) { |
||
154 | $success = false; |
||
155 | $this->messages->enqueue(new FlashMessage( |
||
156 | '', |
||
157 | 'Automatic TYPO3 CMS core update not possible: Update not supported on Windows OS', |
||
158 | FlashMessage::ERROR |
||
159 | )); |
||
160 | } |
||
161 | |||
162 | if ($success) { |
||
163 | // Explicit write check to document root |
||
164 | $file = Environment::getPublicPath() . '/' . StringUtility::getUniqueId('install-core-update-test-'); |
||
165 | $result = @touch($file); |
||
166 | if (!$result) { |
||
167 | $success = false; |
||
168 | $this->messages->enqueue(new FlashMessage( |
||
169 | 'Could not write a file in path "' . Environment::getPublicPath() . '/"!' |
||
170 | . ' Please check your directory status in the “Environment” module under “Directory Status”.', |
||
171 | 'Automatic TYPO3 CMS core update not possible: No write access to document root', |
||
172 | FlashMessage::ERROR |
||
173 | )); |
||
174 | } else { |
||
175 | // Check symlink creation |
||
176 | $link = Environment::getPublicPath() . '/' . StringUtility::getUniqueId('install-core-update-test-'); |
||
177 | @symlink($file, $link); |
||
178 | if (!is_link($link)) { |
||
179 | $success = false; |
||
180 | $this->messages->enqueue(new FlashMessage( |
||
181 | 'Could not create a symbolic link in path "' . Environment::getPublicPath() . '/"!' |
||
182 | . ' Please check your directory status in the “Environment” module under “Directory Status”.', |
||
183 | 'Automatic TYPO3 CMS core update not possible: No symlink creation possible', |
||
184 | FlashMessage::ERROR |
||
185 | )); |
||
186 | } else { |
||
187 | unlink($link); |
||
188 | } |
||
189 | unlink($file); |
||
190 | } |
||
191 | |||
192 | if (!$this->checkCoreFilesAvailable($version)) { |
||
193 | // Explicit write check to upper directory of current core location |
||
194 | $coreLocation = @realpath($this->symlinkToCoreFiles . '/../'); |
||
195 | $file = $coreLocation . '/' . StringUtility::getUniqueId('install-core-update-test-'); |
||
|
|||
196 | $result = @touch($file); |
||
197 | if (!$result) { |
||
198 | $success = false; |
||
199 | $this->messages->enqueue(new FlashMessage( |
||
200 | 'New TYPO3 CMS core should be installed in "' . $coreLocation . '", but this directory is not writable!' |
||
201 | . ' Please check your directory status in the “Environment” module under “Directory Status”.', |
||
202 | 'Automatic TYPO3 CMS core update not possible: No write access to TYPO3 CMS core location', |
||
203 | FlashMessage::ERROR |
||
204 | )); |
||
205 | } else { |
||
206 | unlink($file); |
||
207 | } |
||
208 | } |
||
209 | } |
||
210 | |||
211 | if ($success && !$this->coreVersionService->isInstalledVersionAReleasedVersion()) { |
||
212 | $success = false; |
||
213 | $this->messages->enqueue(new FlashMessage( |
||
214 | 'Your current version is specified as ' . $this->coreVersionService->getInstalledVersion() . '.' |
||
215 | . ' This is a development version and can not be updated automatically. If this is a "git"' |
||
216 | . ' checkout, please update using git directly.', |
||
217 | 'Automatic TYPO3 CMS core update not possible: You are running a development version of TYPO3', |
||
218 | FlashMessage::ERROR |
||
219 | )); |
||
220 | } |
||
221 | |||
222 | return $success; |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * Download the specified version |
||
227 | * |
||
228 | * @param string $version A version to download |
||
229 | * @return bool TRUE on success |
||
230 | */ |
||
231 | public function downloadVersion($version) |
||
232 | { |
||
233 | $success = true; |
||
234 | if ($this->checkCoreFilesAvailable($version)) { |
||
235 | $this->messages->enqueue(new FlashMessage( |
||
236 | '', |
||
237 | 'Skipped download of TYPO3 CMS core. A core source directory already exists in destination path. Using this instead.', |
||
238 | FlashMessage::NOTICE |
||
239 | )); |
||
240 | } else { |
||
241 | $downloadUri = $this->downloadBaseUri . '/' . $version; |
||
242 | $fileLocation = $this->getDownloadTarGzTargetPath($version); |
||
243 | |||
244 | if (@file_exists($fileLocation)) { |
||
245 | $success = false; |
||
246 | $this->messages->enqueue(new FlashMessage( |
||
247 | '', |
||
248 | 'TYPO3 CMS core download exists in download location: ' . PathUtility::stripPathSitePrefix($this->downloadTargetPath), |
||
249 | FlashMessage::ERROR |
||
250 | )); |
||
251 | } else { |
||
252 | $fileContent = GeneralUtility::getUrl($downloadUri); |
||
253 | if (!$fileContent) { |
||
254 | $success = false; |
||
255 | $this->messages->enqueue(new FlashMessage( |
||
256 | 'Failed to download ' . $downloadUri, |
||
257 | 'Download not successful', |
||
258 | FlashMessage::ERROR |
||
259 | )); |
||
260 | } else { |
||
261 | $fileStoreResult = file_put_contents($fileLocation, $fileContent); |
||
262 | if (!$fileStoreResult) { |
||
263 | $success = false; |
||
264 | $this->messages->enqueue(new FlashMessage( |
||
265 | '', |
||
266 | 'Unable to store download content', |
||
267 | FlashMessage::ERROR |
||
268 | )); |
||
269 | } else { |
||
270 | $this->messages->enqueue(new FlashMessage( |
||
271 | '', |
||
272 | 'TYPO3 CMS core download finished' |
||
273 | )); |
||
274 | } |
||
275 | } |
||
276 | } |
||
277 | } |
||
278 | return $success; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Verify checksum of downloaded version |
||
283 | * |
||
284 | * @param string $version A downloaded version to check |
||
285 | * @return bool TRUE on success |
||
286 | */ |
||
287 | public function verifyFileChecksum($version) |
||
288 | { |
||
289 | $success = true; |
||
290 | if ($this->checkCoreFilesAvailable($version)) { |
||
291 | $this->messages->enqueue(new FlashMessage( |
||
292 | '', |
||
293 | 'Verifying existing TYPO3 CMS core checksum is not possible', |
||
294 | FlashMessage::WARNING |
||
295 | )); |
||
296 | } else { |
||
297 | $fileLocation = $this->getDownloadTarGzTargetPath($version); |
||
298 | $expectedChecksum = $this->coreVersionService->getTarGzSha1OfVersion($version); |
||
299 | if (!file_exists($fileLocation)) { |
||
300 | $success = false; |
||
301 | $this->messages->enqueue(new FlashMessage( |
||
302 | '', |
||
303 | 'Downloaded TYPO3 CMS core not found', |
||
304 | FlashMessage::ERROR |
||
305 | )); |
||
306 | } else { |
||
307 | $actualChecksum = sha1_file($fileLocation); |
||
308 | if ($actualChecksum !== $expectedChecksum) { |
||
309 | $success = false; |
||
310 | $this->messages->enqueue(new FlashMessage( |
||
311 | 'The official TYPO3 CMS version system on https://get.typo3.org expects a sha1 checksum of ' |
||
312 | . $expectedChecksum . ' from the content of the downloaded new TYPO3 CMS core version ' . $version . '.' |
||
313 | . ' The actual checksum is ' . $actualChecksum . '. The update is stopped. This may be a' |
||
314 | . ' failed download, an attack, or an issue with the typo3.org infrastructure.', |
||
315 | 'New TYPO3 CMS core checksum mismatch', |
||
316 | FlashMessage::ERROR |
||
317 | )); |
||
318 | } else { |
||
319 | $this->messages->enqueue(new FlashMessage( |
||
320 | '', |
||
321 | 'Checksum verified' |
||
322 | )); |
||
323 | } |
||
324 | } |
||
325 | } |
||
326 | return $success; |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Unpack a downloaded core |
||
331 | * |
||
332 | * @param string $version A version to unpack |
||
333 | * @return bool TRUE on success |
||
334 | */ |
||
335 | public function unpackVersion($version) |
||
336 | { |
||
337 | $success = true; |
||
338 | if ($this->checkCoreFilesAvailable($version)) { |
||
339 | $this->messages->enqueue(new FlashMessage( |
||
340 | '', |
||
341 | 'Unpacking TYPO3 CMS core files skipped', |
||
342 | FlashMessage::NOTICE |
||
343 | )); |
||
344 | } else { |
||
345 | $fileLocation = $this->downloadTargetPath . $version . '.tar.gz'; |
||
346 | if (!@is_file($fileLocation)) { |
||
347 | $success = false; |
||
348 | $this->messages->enqueue(new FlashMessage( |
||
349 | '', |
||
350 | 'Downloaded TYPO3 CMS core not found', |
||
351 | FlashMessage::ERROR |
||
352 | )); |
||
353 | } elseif (@file_exists($this->downloadTargetPath . 'typo3_src-' . $version)) { |
||
354 | $success = false; |
||
355 | $this->messages->enqueue(new FlashMessage( |
||
356 | '', |
||
357 | 'Unpacked TYPO3 CMS core exists in download location: ' . PathUtility::stripPathSitePrefix($this->downloadTargetPath), |
||
358 | FlashMessage::ERROR |
||
359 | )); |
||
360 | } else { |
||
361 | $unpackCommand = 'tar xf ' . escapeshellarg($fileLocation) . ' -C ' . escapeshellarg($this->downloadTargetPath) . ' 2>&1'; |
||
362 | exec($unpackCommand, $output, $errorCode); |
||
363 | if ($errorCode) { |
||
364 | $success = false; |
||
365 | $this->messages->enqueue(new FlashMessage( |
||
366 | '', |
||
367 | 'Unpacking TYPO3 CMS core not successful', |
||
368 | FlashMessage::ERROR |
||
369 | )); |
||
370 | } else { |
||
371 | $removePackedFileResult = unlink($fileLocation); |
||
372 | if (!$removePackedFileResult) { |
||
373 | $success = false; |
||
374 | $this->messages->enqueue(new FlashMessage( |
||
375 | '', |
||
376 | 'Removing packed TYPO3 CMS core not successful', |
||
377 | FlashMessage::ERROR |
||
378 | )); |
||
379 | } else { |
||
380 | $this->messages->enqueue(new FlashMessage( |
||
381 | '', |
||
382 | 'Unpacking TYPO3 CMS core successful' |
||
383 | )); |
||
384 | } |
||
385 | } |
||
386 | } |
||
387 | } |
||
388 | return $success; |
||
389 | } |
||
390 | |||
391 | /** |
||
392 | * Move an unpacked core to its final destination |
||
393 | * |
||
394 | * @param string $version A version to move |
||
395 | * @return bool TRUE on success |
||
396 | */ |
||
397 | public function moveVersion($version) |
||
398 | { |
||
399 | $success = true; |
||
400 | if ($this->checkCoreFilesAvailable($version)) { |
||
401 | $this->messages->enqueue(new FlashMessage( |
||
402 | '', |
||
403 | 'Moving TYPO3 CMS core files skipped', |
||
404 | FlashMessage::NOTICE |
||
405 | )); |
||
406 | } else { |
||
407 | $downloadedCoreLocation = $this->downloadTargetPath . 'typo3_src-' . $version; |
||
408 | $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version; |
||
409 | |||
410 | if (!@is_dir($downloadedCoreLocation)) { |
||
411 | $success = false; |
||
412 | $this->messages->enqueue(new FlashMessage( |
||
413 | '', |
||
414 | 'Unpacked TYPO3 CMS core not found', |
||
415 | FlashMessage::ERROR |
||
416 | )); |
||
417 | } else { |
||
418 | $moveResult = rename($downloadedCoreLocation, $newCoreLocation); |
||
419 | if (!$moveResult) { |
||
420 | $success = false; |
||
421 | $this->messages->enqueue(new FlashMessage( |
||
422 | '', |
||
423 | 'Moving TYPO3 CMS core to ' . $newCoreLocation . ' failed', |
||
424 | FlashMessage::ERROR |
||
425 | )); |
||
426 | } else { |
||
427 | $this->messages->enqueue(new FlashMessage( |
||
428 | '', |
||
429 | 'Moved TYPO3 CMS core to final location' |
||
430 | )); |
||
431 | } |
||
432 | } |
||
433 | } |
||
434 | return $success; |
||
435 | } |
||
436 | |||
437 | /** |
||
438 | * Activate a core version |
||
439 | * |
||
440 | * @param string $version A version to activate |
||
441 | * @return bool TRUE on success |
||
442 | */ |
||
443 | public function activateVersion($version) |
||
444 | { |
||
445 | $newCoreLocation = @realpath($this->symlinkToCoreFiles . '/../') . '/typo3_src-' . $version; |
||
446 | $success = true; |
||
447 | if (!is_dir($newCoreLocation)) { |
||
448 | $success = false; |
||
449 | $this->messages->enqueue(new FlashMessage( |
||
450 | '', |
||
451 | 'New TYPO3 CMS core not found', |
||
452 | FlashMessage::ERROR |
||
453 | )); |
||
454 | } elseif (!is_link($this->symlinkToCoreFiles)) { |
||
455 | $success = false; |
||
456 | $this->messages->enqueue(new FlashMessage( |
||
457 | '', |
||
458 | 'TYPO3 CMS core source directory (typo3_src) is not a link', |
||
459 | FlashMessage::ERROR |
||
460 | )); |
||
461 | } else { |
||
462 | $isCurrentCoreSymlinkAbsolute = PathUtility::isAbsolutePath((string)readlink($this->symlinkToCoreFiles)); |
||
463 | $unlinkResult = unlink($this->symlinkToCoreFiles); |
||
464 | if (!$unlinkResult) { |
||
465 | $success = false; |
||
466 | $this->messages->enqueue(new FlashMessage( |
||
467 | '', |
||
468 | 'Removing old symlink failed', |
||
469 | FlashMessage::ERROR |
||
470 | )); |
||
471 | } else { |
||
472 | if (!$isCurrentCoreSymlinkAbsolute) { |
||
473 | $newCoreLocation = $this->getRelativePath($newCoreLocation); |
||
474 | } |
||
475 | $symlinkResult = symlink($newCoreLocation, $this->symlinkToCoreFiles); |
||
476 | if ($symlinkResult) { |
||
477 | GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive(); |
||
478 | } else { |
||
479 | $success = false; |
||
480 | $this->messages->enqueue(new FlashMessage( |
||
481 | '', |
||
482 | 'Linking new TYPO3 CMS core failed', |
||
483 | FlashMessage::ERROR |
||
484 | )); |
||
485 | } |
||
486 | } |
||
487 | } |
||
488 | return $success; |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * Absolute path of downloaded .tar.gz |
||
493 | * |
||
494 | * @param string $version A version number |
||
495 | * @return string |
||
496 | */ |
||
497 | protected function getDownloadTarGzTargetPath($version) |
||
498 | { |
||
499 | return $this->downloadTargetPath . $version . '.tar.gz'; |
||
500 | } |
||
501 | |||
502 | /** |
||
503 | * Get relative path to TYPO3 source directory from webroot |
||
504 | * |
||
505 | * @param string $absolutePath to TYPO3 source directory |
||
506 | * @return string relative path to TYPO3 source directory |
||
507 | */ |
||
508 | protected function getRelativePath($absolutePath) |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * Check if there is are already core files available |
||
521 | * at the download destination. |
||
522 | * |
||
523 | * @param string $version A version number |
||
524 | * @return bool true when core files are available |
||
525 | */ |
||
526 | protected function checkCoreFilesAvailable($version) |
||
530 | } |
||
531 | } |
||
532 |