1<?php 2 3namespace TYPO3\CMS\Install\Updates; 4 5/* 6 * This file is part of the TYPO3 CMS project. 7 * 8 * It is free software; you can redistribute it and/or modify it under 9 * the terms of the GNU General Public License, either version 2 10 * of the License, or any later version. 11 * 12 * For the full copyright and license information, please read the 13 * LICENSE.txt file that was distributed with this source code. 14 * 15 * The TYPO3 project - inspiring people to share! 16 */ 17 18use Symfony\Component\Console\Output\OutputInterface; 19use TYPO3\CMS\Core\Core\Environment; 20use TYPO3\CMS\Core\Utility\GeneralUtility; 21use TYPO3\CMS\Extbase\Object\ObjectManager; 22use TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility; 23use TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility; 24use TYPO3\CMS\Extensionmanager\Utility\InstallUtility; 25use TYPO3\CMS\Extensionmanager\Utility\ListUtility; 26 27/** 28 * Download extension from TER 29 */ 30abstract class AbstractDownloadExtensionUpdate implements UpgradeWizardInterface, ConfirmableInterface, ChattyInterface 31{ 32 /** 33 * @var string 34 */ 35 protected $repositoryUrl = 'https://typo3.org/fileadmin/ter/@filename'; 36 37 /** 38 * @var OutputInterface 39 */ 40 protected $output; 41 42 /** 43 * @var \TYPO3\CMS\Install\Updates\ExtensionModel 44 */ 45 protected $extension; 46 47 /** 48 * @param OutputInterface $output 49 */ 50 public function setOutput(OutputInterface $output): void 51 { 52 $this->output = $output; 53 } 54 55 /** 56 * Execute the update 57 * Called when a wizard reports that an update is necessary 58 * 59 * @return bool 60 */ 61 public function executeUpdate(): bool 62 { 63 return $this->installExtension($this->extension); 64 } 65 66 /** 67 * This method can be called to install an extension following all proper processes 68 * (e.g. installing in extList, respecting priority, etc.) 69 * 70 * @param \TYPO3\CMS\Install\Updates\ExtensionModel $extension 71 * @return bool whether the installation worked or not 72 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException 73 */ 74 protected function installExtension(ExtensionModel $extension): bool 75 { 76 $updateSuccessful = true; 77 /** @var ObjectManager $objectManager */ 78 $objectManager = GeneralUtility::makeInstance(ObjectManager::class); 79 80 /** @var ListUtility $extensionListUtility */ 81 $extensionListUtility = $objectManager->get(ListUtility::class); 82 $availableExtensions = $extensionListUtility->getAvailableExtensions(); 83 84 $extensionKey = $extension->getKey(); 85 $isExtensionAvailable = !empty($availableExtensions[$extensionKey]); 86 $isComposerMode = Environment::isComposerMode(); 87 88 if (!$isComposerMode && !$isExtensionAvailable) { 89 /** @var TerUtility $extensionTerUtility */ 90 $extensionTerUtility = $objectManager->get(TerUtility::class); 91 $t3xContent = $this->fetchExtension($extensionKey, $extension->getVersionString()); 92 if (empty($t3xContent)) { 93 $updateSuccessful = false; 94 $this->output->writeln('<error>The extension ' . $extensionKey . ' could not be downloaded.</error>'); 95 } 96 $t3xExtracted = $extensionTerUtility->decodeExchangeData($t3xContent); 97 if (empty($t3xExtracted) || !is_array($t3xExtracted) || empty($t3xExtracted['extKey'])) { 98 $updateSuccessful = false; 99 $this->output->writeln('<error>The extension ' . $extensionKey . ' could not be extracted.</error>'); 100 } 101 102 /** @var FileHandlingUtility $extensionFileHandlingUtility */ 103 $extensionFileHandlingUtility = $objectManager->get(FileHandlingUtility::class); 104 $extensionFileHandlingUtility->unpackExtensionFromExtensionDataArray($t3xExtracted); 105 106 // The listUtility now needs to have the regenerated list of packages 107 $extensionListUtility->reloadAvailableExtensions(); 108 } 109 110 if ($isComposerMode && !$isExtensionAvailable) { 111 $updateSuccessful = false; 112 $this->output->writeln( 113 '<warning>The extension ' . $extensionKey 114 . ' can not be downloaded since Composer is used for package management. Please require this ' 115 . 'extension as package via Composer: "composer require ' . $extension->getComposerName() 116 . ':^' . $extension->getVersionString() . '"</warning>' 117 ); 118 } 119 120 if ($updateSuccessful) { 121 /** @var InstallUtility $extensionInstallUtility */ 122 $extensionInstallUtility = $objectManager->get(InstallUtility::class); 123 $extensionInstallUtility->install($extensionKey); 124 } 125 126 return $updateSuccessful; 127 } 128 129 /** 130 * Fetch extension from repository 131 * 132 * @param string $extensionKey The extension key to fetch 133 * @param string $version The version to fetch 134 * @throws \InvalidArgumentException 135 * @return string T3X file content 136 */ 137 protected function fetchExtension($extensionKey, $version): string 138 { 139 if (empty($extensionKey) || empty($version)) { 140 throw new \InvalidArgumentException( 141 'No extension key for fetching an extension was given.', 142 1344687432 143 ); 144 } 145 146 $filename = $extensionKey[0] . '/' . $extensionKey[1] . '/' . $extensionKey . '_' . $version . '.t3x'; 147 $url = str_replace('@filename', $filename, $this->repositoryUrl); 148 149 return $this->fetchUrl($url); 150 } 151 152 /** 153 * Open an URL and return the response 154 * This wrapper method is required to try several download methods if 155 * the configuration is not valid or initially written by the installer. 156 * 157 * @param string $url The URL to file 158 * @throws \Exception 159 * @throws \InvalidArgumentException 160 * @return string File content 161 */ 162 protected function fetchUrl($url): string 163 { 164 if (empty($url)) { 165 throw new \InvalidArgumentException( 166 'No URL for downloading an extension given.', 167 1344687436 168 ); 169 } 170 171 $fileContent = GeneralUtility::getUrl($url); 172 173 // Can not fetch url, throw an exception 174 if ($fileContent === false) { 175 throw new \RuntimeException( 176 'Can not fetch URL "' . $url . '". Possible reasons are network problems or misconfiguration.', 177 1344685036 178 ); 179 } 180 181 return $fileContent; 182 } 183} 184