[TASK] Re-style record list search box
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Classes / Controller / DownloadController.php
1 <?php
2
3 /*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16 namespace TYPO3\CMS\Extensionmanager\Controller;
17
18 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
19 use TYPO3\CMS\Core\Messaging\AbstractMessage;
20 use TYPO3\CMS\Core\Messaging\FlashMessage;
21 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Extbase\Http\ForwardResponse;
24 use TYPO3\CMS\Extbase\Mvc\View\JsonView;
25 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
26 use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
27 use TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository;
28 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
29 use TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService;
30 use TYPO3\CMS\Fluid\View\TemplateView;
31
32 /**
33 * Controller for actions related to the TER download of an extension
34 * @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
35 */
36 class DownloadController extends AbstractController
37 {
38 /**
39 * @var ExtensionRepository
40 */
41 protected $extensionRepository;
42
43 /**
44 * @var ExtensionManagementService
45 */
46 protected $managementService;
47
48 /**
49 * @var string
50 */
51 protected $defaultViewObjectName = JsonView::class;
52
53 /**
54 * @var JsonView
55 */
56 protected $view;
57
58 /**
59 * @param ExtensionRepository $extensionRepository
60 */
61 public function injectExtensionRepository(ExtensionRepository $extensionRepository)
62 {
63 $this->extensionRepository = $extensionRepository;
64 }
65
66 /**
67 * @param ExtensionManagementService $managementService
68 */
69 public function injectManagementService(ExtensionManagementService $managementService)
70 {
71 $this->managementService = $managementService;
72 }
73
74 /**
75 * Defines which view object should be used for the installFromTer action
76 */
77 protected function initializeInstallFromTerAction()
78 {
79 $this->defaultViewObjectName = TemplateView::class;
80 }
81
82 /**
83 * Check extension dependencies
84 *
85 * @param Extension $extension
86 * @throws \Exception
87 */
88 public function checkDependenciesAction(Extension $extension)
89 {
90 $message = '';
91 $title = '';
92 $hasDependencies = false;
93 $hasErrors = false;
94 $dependencyTypes = null;
95 $configuration = [
96 'value' => [
97 'dependencies' => [],
98 ],
99 ];
100 $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
101 if (!$isAutomaticInstallationEnabled) {
102 // if automatic installation is deactivated, no dependency check is needed (download only)
103 $action = 'installExtensionWithoutSystemDependencyCheck';
104 } else {
105 $action = 'installFromTer';
106 try {
107 $dependencyTypes = $this->managementService->getAndResolveDependencies($extension);
108 if (!empty($dependencyTypes)) {
109 $hasDependencies = true;
110 $message = '<p>' . $this->translate('downloadExtension.dependencies.headline') . '</p>';
111 foreach ($dependencyTypes as $dependencyType => $dependencies) {
112 $extensions = '';
113 foreach ($dependencies as $extensionKey => $dependency) {
114 if (!isset($configuration['value']['dependencies'][$dependencyType])) {
115 $configuration['value']['dependencies'][$dependencyType] = [];
116 }
117 $configuration['value']['dependencies'][$dependencyType][$extensionKey] = [
118 '_exclude' => [
119 'categoryIndexFromStringOrNumber',
120 ],
121 ];
122 $extensions .= $this->translate(
123 'downloadExtension.dependencies.extensionWithVersion',
124 [
125 $extensionKey, $dependency->getVersion()
126 ]
127 ) . '<br />';
128 }
129 $message .= $this->translate(
130 'downloadExtension.dependencies.typeHeadline',
131 [
132 $this->translate('downloadExtension.dependencyType.' . $dependencyType),
133 $extensions
134 ]
135 );
136 }
137 $title = $this->translate('downloadExtension.dependencies.resolveAutomatically');
138 }
139 } catch (\Exception $e) {
140 $hasErrors = true;
141 $title = $this->translate('downloadExtension.dependencies.errorTitle');
142 $message = $e->getMessage();
143 }
144 }
145
146 $url = $this->uriBuilder->uriFor(
147 $action,
148 ['extension' => $extension->getUid(), 'format' => 'json'],
149 'Download'
150 );
151 $this->view->setConfiguration($configuration);
152 $this->view->assign('value', [
153 'dependencies' => $dependencyTypes,
154 'url' => $url,
155 'message' => $message,
156 'hasErrors' => $hasErrors,
157 'hasDependencies' => $hasDependencies,
158 'title' => $title
159 ]);
160 }
161
162 /**
163 * Install an extension from TER action
164 *
165 * @param Extension $extension
166 * @param string $downloadPath
167 */
168 public function installFromTerAction(Extension $extension, $downloadPath = 'Local')
169 {
170 [$result, $errorMessages] = $this->installFromTer($extension, $downloadPath);
171 $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
172 $this->view
173 ->assign('result', $result)
174 ->assign('extension', $extension)
175 ->assign('installationTypeLanguageKey', $isAutomaticInstallationEnabled ? '' : '.downloadOnly')
176 ->assign('unresolvedDependencies', $errorMessages);
177 }
178
179 /**
180 * Check extension dependencies with special dependencies
181 *
182 * @param Extension $extension
183 * @throws \Exception
184 */
185 public function installExtensionWithoutSystemDependencyCheckAction(Extension $extension)
186 {
187 $this->managementService->setSkipDependencyCheck(true);
188 return (new ForwardResponse('installFromTer'))->withArguments(['extension' => $extension, 'downloadPath' => 'Local']);
189 }
190
191 /**
192 * Action for installing a distribution -
193 * redirects directly to configuration after installing
194 *
195 * @param Extension $extension
196 */
197 public function installDistributionAction(Extension $extension)
198 {
199 if (!ExtensionManagementUtility::isLoaded('impexp')) {
200 return (new ForwardResponse('distributions'))->withControllerName('List');
201 }
202 [$result, $errorMessages] = $this->installFromTer($extension);
203 if ($errorMessages) {
204 foreach ($errorMessages as $extensionKey => $messages) {
205 foreach ($messages as $message) {
206 /** @var string $message */
207 $this->addFlashMessage(
208 $message['message'],
209 LocalizationUtility::translate(
210 'distribution.error.headline',
211 'extensionmanager',
212 [$extensionKey]
213 ) ?? '',
214 AbstractMessage::ERROR
215 );
216 }
217 }
218
219 // Redirect back to distributions list action
220 $this->redirect(
221 'distributions',
222 'List'
223 );
224 } else {
225 // FlashMessage that extension is installed
226 $this->addFlashMessage(
227 LocalizationUtility::translate(
228 'distribution.welcome.message',
229 'extensionmanager',
230 [$extension->getExtensionKey()]
231 ) ?? '',
232 LocalizationUtility::translate('distribution.welcome.headline', 'extensionmanager') ?? ''
233 );
234
235 // Redirect to show action
236 $this->redirect(
237 'show',
238 'Distribution',
239 null,
240 ['extension' => $extension]
241 );
242 }
243 }
244
245 /**
246 * Update an extension. Makes no sanity check but directly searches highest
247 * available version from TER and updates. Update check is done by the list
248 * already. This method should only be called if we are sure that there is
249 * an update.
250 *
251 * @return string
252 */
253 protected function updateExtensionAction()
254 {
255 $extensionKey = $this->request->getArgument('extension');
256 $version = $this->request->getArgument('version');
257 $extension = $this->extensionRepository->findOneByExtensionKeyAndVersion($extensionKey, $version);
258 if (!$extension instanceof Extension) {
259 $extension = $this->extensionRepository->findHighestAvailableVersion($extensionKey);
260 }
261 $installedExtensions = ExtensionManagementUtility::getLoadedExtensionListArray();
262 try {
263 if (in_array($extensionKey, $installedExtensions, true)) {
264 // To resolve new dependencies the extension is installed again
265 $this->managementService->installExtension($extension);
266 } else {
267 $this->managementService->downloadMainExtension($extension);
268 }
269 $this->addFlashMessage(
270 $this->translate('extensionList.updateFlashMessage.body', [$extensionKey]),
271 $this->translate('extensionList.updateFlashMessage.title')
272 );
273 } catch (\Exception $e) {
274 $this->addFlashMessage($e->getMessage(), '', FlashMessage::ERROR);
275 }
276
277 return '';
278 }
279
280 /**
281 * Show update comments for extensions that can be updated.
282 * Fetches update comments for all versions between the current
283 * installed and the highest version.
284 */
285 protected function updateCommentForUpdatableVersionsAction()
286 {
287 $extensionKey = $this->request->getArgument('extension');
288 $versionStart = $this->request->getArgument('integerVersionStart');
289 $versionStop = $this->request->getArgument('integerVersionStop');
290 $updateComments = [];
291 /** @var Extension[] $updatableVersions */
292 $updatableVersions = $this->extensionRepository->findByVersionRangeAndExtensionKeyOrderedByVersion(
293 $extensionKey,
294 $versionStart,
295 $versionStop,
296 false
297 );
298 $highestPossibleVersion = false;
299
300 foreach ($updatableVersions as $updatableVersion) {
301 if ($highestPossibleVersion === false) {
302 $highestPossibleVersion = $updatableVersion->getVersion();
303 }
304 $updateComments[$updatableVersion->getVersion()] = $updatableVersion->getUpdateComment();
305 }
306
307 $this->view->assign('value', [
308 'updateComments' => $updateComments,
309 'url' => $this->uriBuilder->uriFor(
310 'updateExtension',
311 ['extension' => $extensionKey, 'version' => $highestPossibleVersion]
312 )
313 ]);
314 }
315
316 /**
317 * Install an extension from TER
318 * Downloads the extension, resolves dependencies and installs it
319 *
320 * @param Extension $extension
321 * @param string $downloadPath
322 * @return array
323 */
324 protected function installFromTer(Extension $extension, $downloadPath = 'Local')
325 {
326 $result = false;
327 $errorMessages = [];
328 try {
329 $this->managementService->setDownloadPath($downloadPath);
330 $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
331 $this->managementService->setAutomaticInstallationEnabled($isAutomaticInstallationEnabled);
332 if (($result = $this->managementService->installExtension($extension)) === false) {
333 $errorMessages = $this->managementService->getDependencyErrors();
334 }
335 } catch (ExtensionManagerException $e) {
336 $errorMessages = [
337 $extension->getExtensionKey() => [
338 [
339 'code' => $e->getCode(),
340 'message' => $e->getMessage(),
341 ]
342 ],
343 ];
344 }
345
346 return [$result, $errorMessages];
347 }
348 }