149276be02fe1416304e29cbcdb2298589905d93
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Classes / Service / ExtensionManagementService.php
1 <?php
2 namespace TYPO3\CMS\Extensionmanager\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\Extbase\Object\ObjectManager;
19 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
20 use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
21
22 /**
23 * Service class for managing multiple step processes (dependencies for example)
24 */
25 class ExtensionManagementService implements \TYPO3\CMS\Core\SingletonInterface
26 {
27 /**
28 * @var \TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue
29 */
30 protected $downloadQueue;
31
32 /**
33 * @var \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility
34 */
35 protected $dependencyUtility;
36
37 /**
38 * @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility
39 */
40 protected $installUtility;
41
42 /**
43 * @var \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility
44 */
45 protected $extensionModelUtility;
46
47 /**
48 * @var \TYPO3\CMS\Extensionmanager\Utility\DownloadUtility
49 */
50 protected $downloadUtility;
51
52 /**
53 * @var bool
54 */
55 protected $automaticInstallationEnabled = true;
56
57 /**
58 * @var bool
59 */
60 protected $skipDependencyCheck = false;
61
62 /**
63 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue $downloadQueue
64 */
65 public function injectDownloadQueue(\TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue $downloadQueue)
66 {
67 $this->downloadQueue = $downloadQueue;
68 }
69
70 /**
71 * @param \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility
72 */
73 public function injectDependencyUtility(\TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility)
74 {
75 $this->dependencyUtility = $dependencyUtility;
76 }
77
78 /**
79 * @param \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility
80 */
81 public function injectInstallUtility(\TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility)
82 {
83 $this->installUtility = $installUtility;
84 }
85
86 /**
87 * @param \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility $extensionModelUtility
88 */
89 public function injectExtensionModelUtility(\TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility $extensionModelUtility)
90 {
91 $this->extensionModelUtility = $extensionModelUtility;
92 }
93
94 /**
95 * @param \TYPO3\CMS\Extensionmanager\Utility\DownloadUtility $downloadUtility
96 */
97 public function injectDownloadUtility(\TYPO3\CMS\Extensionmanager\Utility\DownloadUtility $downloadUtility)
98 {
99 $this->downloadUtility = $downloadUtility;
100 }
101
102 /**
103 * @param string $extensionKey
104 * @return void
105 */
106 public function markExtensionForInstallation($extensionKey)
107 {
108 // We have to check for dependencies of the extension first, before marking it for installation
109 // because this extension might have dependencies, which need to be installed first
110 $this->installUtility->reloadAvailableExtensions();
111 $extension = $this->getExtension($extensionKey);
112 $this->dependencyUtility->checkDependencies($extension);
113 $this->downloadQueue->addExtensionToInstallQueue($extension);
114 }
115
116 /**
117 * Mark an extension for copy
118 *
119 * @param string $extensionKey
120 * @param string $sourceFolder
121 * @return void
122 */
123 public function markExtensionForCopy($extensionKey, $sourceFolder)
124 {
125 $this->downloadQueue->addExtensionToCopyQueue($extensionKey, $sourceFolder);
126 }
127
128 /**
129 * Mark an extension for download
130 *
131 * @param Extension $extension
132 * @return void
133 */
134 public function markExtensionForDownload(Extension $extension)
135 {
136 // We have to check for dependencies of the extension first, before marking it for download
137 // because this extension might have dependencies, which need to be downloaded and installed first
138 $this->dependencyUtility->checkDependencies($extension);
139 if (!$this->dependencyUtility->hasDependencyErrors()) {
140 $this->downloadQueue->addExtensionToQueue($extension);
141 }
142 }
143
144 /**
145 * @param Extension $extension
146 * @return void
147 */
148 public function markExtensionForUpdate(Extension $extension)
149 {
150 // We have to check for dependencies of the extension first, before marking it for download
151 // because this extension might have dependencies, which need to be downloaded and installed first
152 $this->dependencyUtility->checkDependencies($extension);
153 $this->downloadQueue->addExtensionToQueue($extension, 'update');
154 }
155
156 /**
157 * Enables or disables the dependency check for system environment (PHP, TYPO3) before extension installation
158 *
159 * @param bool $skipDependencyCheck
160 */
161 public function setSkipDependencyCheck($skipDependencyCheck)
162 {
163 $this->skipDependencyCheck = $skipDependencyCheck;
164 }
165
166 /**
167 * @param bool $automaticInstallationEnabled
168 */
169 public function setAutomaticInstallationEnabled($automaticInstallationEnabled)
170 {
171 $this->automaticInstallationEnabled = (bool)$automaticInstallationEnabled;
172 }
173
174 /**
175 * Install the extension
176 *
177 * @param Extension $extension
178 * @return bool|array Returns FALSE if dependencies cannot be resolved, otherwise array with installation information
179 */
180 public function installExtension(Extension $extension)
181 {
182 $this->downloadExtension($extension);
183 if (!$this->checkDependencies($extension)) {
184 return false;
185 }
186
187 $updatedDependencies = [];
188 $installedDependencies = [];
189 $queue = $this->downloadQueue->getExtensionQueue();
190 $copyQueue = $this->downloadQueue->getExtensionCopyStorage();
191
192 if (!empty($copyQueue)) {
193 $this->copyDependencies($copyQueue);
194 }
195 $downloadedDependencies = [];
196 if (array_key_exists('download', $queue)) {
197 $downloadedDependencies = $this->downloadDependencies($queue['download']);
198 }
199 if ($this->automaticInstallationEnabled) {
200 if (array_key_exists('update', $queue)) {
201 $this->downloadDependencies($queue['update']);
202 $updatedDependencies = $this->uninstallDependenciesToBeUpdated($queue['update']);
203 }
204 // add extension at the end of the download queue
205 $this->downloadQueue->addExtensionToInstallQueue($extension);
206 $installQueue = $this->downloadQueue->getExtensionInstallStorage();
207 if (!empty($installQueue)) {
208 $installedDependencies = $this->installDependencies($installQueue);
209 }
210 }
211 return array_merge($downloadedDependencies, $updatedDependencies, $installedDependencies);
212 }
213
214 /**
215 * Returns the unresolved dependency errors
216 *
217 * @return array
218 */
219 public function getDependencyErrors()
220 {
221 return $this->dependencyUtility->getDependencyErrors();
222 }
223
224 /**
225 * @param string $extensionKey
226 * @return Extension
227 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
228 */
229 public function getExtension($extensionKey)
230 {
231 return $this->extensionModelUtility->mapExtensionArrayToModel(
232 $this->installUtility->enrichExtensionWithDetails($extensionKey)
233 );
234 }
235
236 /**
237 * Checks if an extension is available in the system
238 *
239 * @param string $extensionKey
240 * @return bool
241 */
242 public function isAvailable($extensionKey)
243 {
244 return $this->installUtility->isAvailable($extensionKey);
245 }
246
247 /**
248 * @param string $extensionKey
249 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageStateException if the package isn't available
250 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageKeyException if an invalid package key was passed
251 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException if an invalid package path was passed
252 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageManifestException if no extension configuration file could be found
253 */
254 public function reloadPackageInformation($extensionKey)
255 {
256 $this->installUtility->reloadPackageInformation($extensionKey);
257 }
258
259 /**
260 * Download an extension
261 *
262 * @param Extension $extension
263 */
264 protected function downloadExtension(Extension $extension)
265 {
266 $this->downloadMainExtension($extension);
267 $this->setInExtensionRepository($extension->getExtensionKey());
268 }
269
270 /**
271 * Check dependencies for an extension and its required extensions
272 *
273 * @param Extension $extension
274 * @return bool Returns TRUE if all dependencies can be resolved, otherwise FALSE
275 */
276 protected function checkDependencies(Extension $extension)
277 {
278 $this->dependencyUtility->setSkipDependencyCheck($this->skipDependencyCheck);
279 $this->dependencyUtility->checkDependencies($extension);
280
281 return !$this->dependencyUtility->hasDependencyErrors();
282 }
283
284 /**
285 * Sets the path to the repository in an extension
286 * (Initialisation/Extensions) depending on the extension
287 * that is currently installed
288 *
289 * @param string $extensionKey
290 */
291 protected function setInExtensionRepository($extensionKey)
292 {
293 $paths = Extension::returnInstallPaths();
294 $path = $paths[$this->downloadUtility->getDownloadPath()];
295 $localExtensionStorage = $path . $extensionKey . '/Initialisation/Extensions/';
296 $this->dependencyUtility->setLocalExtensionStorage($localExtensionStorage);
297 }
298
299 /**
300 * Copies locally provided extensions to typo3conf/ext
301 *
302 * @param array $copyQueue
303 * @return void
304 */
305 protected function copyDependencies(array $copyQueue)
306 {
307 $installPaths = Extension::returnAllowedInstallPaths();
308 foreach ($copyQueue as $extensionKey => $sourceFolder) {
309 $destination = $installPaths['Local'] . $extensionKey;
310 GeneralUtility::mkdir($destination);
311 GeneralUtility::copyDirectory($sourceFolder . $extensionKey, $destination);
312 $this->markExtensionForInstallation($extensionKey);
313 $this->downloadQueue->removeExtensionFromCopyQueue($extensionKey);
314 }
315 }
316
317 /**
318 * Uninstall extensions that will be updated
319 * This is not strictly necessary but cleaner all in all
320 *
321 * @param Extension[] $updateQueue
322 * @return array
323 */
324 protected function uninstallDependenciesToBeUpdated(array $updateQueue)
325 {
326 $resolvedDependencies = [];
327 foreach ($updateQueue as $extensionToUpdate) {
328 $this->installUtility->uninstall($extensionToUpdate->getExtensionKey());
329 $resolvedDependencies['updated'][$extensionToUpdate->getExtensionKey()] = $extensionToUpdate;
330 }
331 return $resolvedDependencies;
332 }
333
334 /**
335 * Install dependent extensions
336 *
337 * @param array $installQueue
338 * @return array
339 */
340 protected function installDependencies(array $installQueue)
341 {
342 if (!empty($installQueue)) {
343 $this->emitWillInstallExtensionsSignal($installQueue);
344 }
345 $resolvedDependencies = [];
346 foreach ($installQueue as $extensionKey => $_) {
347 $this->installUtility->install($extensionKey);
348 $this->emitHasInstalledExtensionSignal($extensionKey);
349 if (!is_array($resolvedDependencies['installed'])) {
350 $resolvedDependencies['installed'] = [];
351 }
352 $resolvedDependencies['installed'][$extensionKey] = $extensionKey;
353 }
354 return $resolvedDependencies;
355 }
356
357 /**
358 * Download dependencies
359 * expects an array of extension objects to download
360 *
361 * @param Extension[] $downloadQueue
362 * @return array
363 */
364 protected function downloadDependencies(array $downloadQueue)
365 {
366 $resolvedDependencies = [];
367 foreach ($downloadQueue as $extensionToDownload) {
368 $this->downloadUtility->download($extensionToDownload);
369 $this->downloadQueue->removeExtensionFromQueue($extensionToDownload);
370 $resolvedDependencies['downloaded'][$extensionToDownload->getExtensionKey()] = $extensionToDownload;
371 $this->markExtensionForInstallation($extensionToDownload->getExtensionKey());
372 }
373 return $resolvedDependencies;
374 }
375
376 /**
377 * Get and resolve dependencies
378 *
379 * @param Extension $extension
380 * @return array
381 */
382 public function getAndResolveDependencies(Extension $extension)
383 {
384 $this->dependencyUtility->setSkipDependencyCheck($this->skipDependencyCheck);
385 $this->dependencyUtility->checkDependencies($extension);
386 $installQueue = $this->downloadQueue->getExtensionInstallStorage();
387 if (is_array($installQueue) && !empty($installQueue)) {
388 $installQueue = ['install' => $installQueue];
389 }
390 return array_merge($this->downloadQueue->getExtensionQueue(), $installQueue);
391 }
392
393 /**
394 * Downloads the extension the user wants to install
395 * This is separated from downloading the dependencies
396 * as an extension is able to provide it's own dependencies
397 *
398 * @param Extension $extension
399 * @return void
400 */
401 public function downloadMainExtension(Extension $extension)
402 {
403 // The extension object has a uid if the extension is not present in the system
404 // or an update of a present extension is triggered.
405 if ($extension->getUid()) {
406 $this->downloadUtility->download($extension);
407 }
408 }
409
410 /**
411 * @param array $installQueue
412 */
413 protected function emitWillInstallExtensionsSignal(array $installQueue)
414 {
415 $this->getSignalSlotDispatcher()->dispatch(__CLASS__, 'willInstallExtensions', [$installQueue]);
416 }
417
418 /**
419 * @param string $extensionKey
420 */
421 protected function emitHasInstalledExtensionSignal($extensionKey)
422 {
423 $this->getSignalSlotDispatcher()->dispatch(__CLASS__, 'hasInstalledExtensions', [$extensionKey]);
424 }
425
426 /**
427 * Get the SignalSlot dispatcher
428 *
429 * @return Dispatcher
430 */
431 protected function getSignalSlotDispatcher()
432 {
433 if (!isset($this->signalSlotDispatcher)) {
434 $this->signalSlotDispatcher = GeneralUtility::makeInstance(ObjectManager::class)
435 ->get(Dispatcher::class);
436 }
437 return $this->signalSlotDispatcher;
438 }
439 }