2 namespace TYPO3\CMS\Extensionmanager\Service
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
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
;
23 * Service class for managing multiple step processes (dependencies for example)
25 class ExtensionManagementService
implements \TYPO3\CMS\Core\SingletonInterface
28 * @var \TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue
30 protected $downloadQueue;
33 * @var \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility
35 protected $dependencyUtility;
38 * @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility
40 protected $installUtility;
43 * @var \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility
45 protected $extensionModelUtility;
48 * @var \TYPO3\CMS\Extensionmanager\Utility\DownloadUtility
50 protected $downloadUtility;
55 protected $automaticInstallationEnabled = true;
60 protected $skipDependencyCheck = false;
63 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue $downloadQueue
65 public function injectDownloadQueue(\TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue
$downloadQueue)
67 $this->downloadQueue
= $downloadQueue;
71 * @param \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility
73 public function injectDependencyUtility(\TYPO3\CMS\Extensionmanager\Utility\DependencyUtility
$dependencyUtility)
75 $this->dependencyUtility
= $dependencyUtility;
79 * @param \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility
81 public function injectInstallUtility(\TYPO3\CMS\Extensionmanager\Utility\InstallUtility
$installUtility)
83 $this->installUtility
= $installUtility;
87 * @param \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility $extensionModelUtility
89 public function injectExtensionModelUtility(\TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility
$extensionModelUtility)
91 $this->extensionModelUtility
= $extensionModelUtility;
95 * @param \TYPO3\CMS\Extensionmanager\Utility\DownloadUtility $downloadUtility
97 public function injectDownloadUtility(\TYPO3\CMS\Extensionmanager\Utility\DownloadUtility
$downloadUtility)
99 $this->downloadUtility
= $downloadUtility;
103 * @param string $extensionKey
105 public function markExtensionForInstallation($extensionKey)
107 // We have to check for dependencies of the extension first, before marking it for installation
108 // because this extension might have dependencies, which need to be installed first
109 $this->installUtility
->reloadAvailableExtensions();
110 $extension = $this->getExtension($extensionKey);
111 $this->dependencyUtility
->checkDependencies($extension);
112 $this->downloadQueue
->addExtensionToInstallQueue($extension);
116 * Mark an extension for copy
118 * @param string $extensionKey
119 * @param string $sourceFolder
121 public function markExtensionForCopy($extensionKey, $sourceFolder)
123 $this->downloadQueue
->addExtensionToCopyQueue($extensionKey, $sourceFolder);
127 * Mark an extension for download
129 * @param Extension $extension
131 public function markExtensionForDownload(Extension
$extension)
133 // We have to check for dependencies of the extension first, before marking it for download
134 // because this extension might have dependencies, which need to be downloaded and installed first
135 $this->dependencyUtility
->checkDependencies($extension);
136 if (!$this->dependencyUtility
->hasDependencyErrors()) {
137 $this->downloadQueue
->addExtensionToQueue($extension);
142 * @param Extension $extension
144 public function markExtensionForUpdate(Extension
$extension)
146 // We have to check for dependencies of the extension first, before marking it for download
147 // because this extension might have dependencies, which need to be downloaded and installed first
148 $this->dependencyUtility
->checkDependencies($extension);
149 $this->downloadQueue
->addExtensionToQueue($extension, 'update');
153 * Enables or disables the dependency check for system environment (PHP, TYPO3) before extension installation
155 * @param bool $skipDependencyCheck
157 public function setSkipDependencyCheck($skipDependencyCheck)
159 $this->skipDependencyCheck
= $skipDependencyCheck;
163 * @param bool $automaticInstallationEnabled
165 public function setAutomaticInstallationEnabled($automaticInstallationEnabled)
167 $this->automaticInstallationEnabled
= (bool)$automaticInstallationEnabled;
171 * Install the extension
173 * @param Extension $extension
174 * @return bool|array Returns FALSE if dependencies cannot be resolved, otherwise array with installation information
176 public function installExtension(Extension
$extension)
178 $this->downloadExtension($extension);
179 if (!$this->checkDependencies($extension)) {
183 $downloadedDependencies = [];
184 $updatedDependencies = [];
187 // First resolve all dependencies and the sub-dependencies until all queues are empty as new extensions might be
189 // Extensions have to be installed in reverse order. Extensions which were added at last are dependencies of
190 // earlier ones and need to be available before
191 while (!$this->downloadQueue
->isCopyQueueEmpty()
192 ||
!$this->downloadQueue
->isQueueEmpty('download')
193 ||
!$this->downloadQueue
->isQueueEmpty('update')
195 // First copy all available extension
196 // This might change other queues again
197 $copyQueue = $this->downloadQueue
->resetExtensionCopyStorage();
198 if (!empty($copyQueue)) {
199 $this->copyDependencies($copyQueue);
201 $installQueue = array_merge($this->downloadQueue
->resetExtensionInstallStorage(), $installQueue);
202 // Get download and update information
203 $queue = $this->downloadQueue
->resetExtensionQueue();
204 if (!empty($queue['download'])) {
205 $downloadedDependencies = array_merge($downloadedDependencies, $this->downloadDependencies($queue['download']));
207 $installQueue = array_merge($this->downloadQueue
->resetExtensionInstallStorage(), $installQueue);
208 if ($this->automaticInstallationEnabled
) {
209 if (!empty($queue['update'])) {
210 $this->downloadDependencies($queue['update']);
211 $updatedDependencies = array_merge($updatedDependencies, $this->uninstallDependenciesToBeUpdated($queue['update']));
213 $installQueue = array_merge($this->downloadQueue
->resetExtensionInstallStorage(), $installQueue);
217 // If there were any dependency errors we have to abort here
218 if ($this->dependencyUtility
->hasDependencyErrors()) {
222 // Attach extension to install queue
223 $this->downloadQueue
->addExtensionToInstallQueue($extension);
224 $installQueue +
= $this->downloadQueue
->resetExtensionInstallStorage();
225 $installedDependencies = $this->installDependencies($installQueue);
227 return array_merge($downloadedDependencies, $updatedDependencies, $installedDependencies);
231 * Returns the unresolved dependency errors
235 public function getDependencyErrors()
237 return $this->dependencyUtility
->getDependencyErrors();
241 * @param string $extensionKey
243 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
245 public function getExtension($extensionKey)
247 return $this->extensionModelUtility
->mapExtensionArrayToModel(
248 $this->installUtility
->enrichExtensionWithDetails($extensionKey)
253 * Checks if an extension is available in the system
255 * @param string $extensionKey
258 public function isAvailable($extensionKey)
260 return $this->installUtility
->isAvailable($extensionKey);
264 * @param string $extensionKey
265 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageStateException if the package isn't available
266 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageKeyException if an invalid package key was passed
267 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException if an invalid package path was passed
268 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageManifestException if no extension configuration file could be found
270 public function reloadPackageInformation($extensionKey)
272 $this->installUtility
->reloadPackageInformation($extensionKey);
276 * Download an extension
278 * @param Extension $extension
280 protected function downloadExtension(Extension
$extension)
282 $this->downloadMainExtension($extension);
283 $this->setInExtensionRepository($extension->getExtensionKey());
287 * Check dependencies for an extension and its required extensions
289 * @param Extension $extension
290 * @return bool Returns TRUE if all dependencies can be resolved, otherwise FALSE
292 protected function checkDependencies(Extension
$extension)
294 $this->dependencyUtility
->setSkipDependencyCheck($this->skipDependencyCheck
);
295 $this->dependencyUtility
->checkDependencies($extension);
297 return !$this->dependencyUtility
->hasDependencyErrors();
301 * Sets the path to the repository in an extension
302 * (Initialisation/Extensions) depending on the extension
303 * that is currently installed
305 * @param string $extensionKey
307 protected function setInExtensionRepository($extensionKey)
309 $paths = Extension
::returnInstallPaths();
310 $path = $paths[$this->downloadUtility
->getDownloadPath()];
311 $localExtensionStorage = $path . $extensionKey . '/Initialisation/Extensions/';
312 $this->dependencyUtility
->setLocalExtensionStorage($localExtensionStorage);
316 * Copies locally provided extensions to typo3conf/ext
318 * @param array $copyQueue
320 protected function copyDependencies(array $copyQueue)
322 $installPaths = Extension
::returnAllowedInstallPaths();
323 foreach ($copyQueue as $extensionKey => $sourceFolder) {
324 $destination = $installPaths['Local'] . $extensionKey;
325 GeneralUtility
::mkdir($destination);
326 GeneralUtility
::copyDirectory($sourceFolder . $extensionKey, $destination);
327 $this->markExtensionForInstallation($extensionKey);
328 $this->downloadQueue
->removeExtensionFromCopyQueue($extensionKey);
333 * Uninstall extensions that will be updated
334 * This is not strictly necessary but cleaner all in all
336 * @param Extension[] $updateQueue
339 protected function uninstallDependenciesToBeUpdated(array $updateQueue)
341 $resolvedDependencies = [];
342 foreach ($updateQueue as $extensionToUpdate) {
343 $this->installUtility
->uninstall($extensionToUpdate->getExtensionKey());
344 $resolvedDependencies['updated'][$extensionToUpdate->getExtensionKey()] = $extensionToUpdate;
346 return $resolvedDependencies;
350 * Install dependent extensions
352 * @param array $installQueue
355 protected function installDependencies(array $installQueue)
357 if (!empty($installQueue)) {
358 $this->emitWillInstallExtensionsSignal($installQueue);
360 $resolvedDependencies = [];
361 foreach ($installQueue as $extensionKey => $_) {
362 $this->installUtility
->install($extensionKey);
363 $this->emitHasInstalledExtensionSignal($extensionKey);
364 if (!isset($resolvedDependencies['installed']) ||
!is_array($resolvedDependencies['installed'])) {
365 $resolvedDependencies['installed'] = [];
367 $resolvedDependencies['installed'][$extensionKey] = $extensionKey;
369 return $resolvedDependencies;
373 * Download dependencies
374 * expects an array of extension objects to download
376 * @param Extension[] $downloadQueue
379 protected function downloadDependencies(array $downloadQueue)
381 $resolvedDependencies = [];
382 foreach ($downloadQueue as $extensionToDownload) {
383 $this->downloadUtility
->download($extensionToDownload);
384 $this->downloadQueue
->removeExtensionFromQueue($extensionToDownload);
385 $resolvedDependencies['downloaded'][$extensionToDownload->getExtensionKey()] = $extensionToDownload;
386 $this->markExtensionForInstallation($extensionToDownload->getExtensionKey());
388 return $resolvedDependencies;
392 * Get and resolve dependencies
394 * @param Extension $extension
397 public function getAndResolveDependencies(Extension
$extension)
399 $this->dependencyUtility
->setSkipDependencyCheck($this->skipDependencyCheck
);
400 $this->dependencyUtility
->checkDependencies($extension);
401 $installQueue = $this->downloadQueue
->getExtensionInstallStorage();
402 if (is_array($installQueue) && !empty($installQueue)) {
403 $installQueue = ['install' => $installQueue];
405 return array_merge($this->downloadQueue
->getExtensionQueue(), $installQueue);
409 * Downloads the extension the user wants to install
410 * This is separated from downloading the dependencies
411 * as an extension is able to provide it's own dependencies
413 * @param Extension $extension
415 public function downloadMainExtension(Extension
$extension)
417 // The extension object has a uid if the extension is not present in the system
418 // or an update of a present extension is triggered.
419 if ($extension->getUid()) {
420 $this->downloadUtility
->download($extension);
425 * @param array $installQueue
427 protected function emitWillInstallExtensionsSignal(array $installQueue)
429 $this->getSignalSlotDispatcher()->dispatch(__CLASS__
, 'willInstallExtensions', [$installQueue]);
433 * @param string $extensionKey
435 protected function emitHasInstalledExtensionSignal($extensionKey)
437 $this->getSignalSlotDispatcher()->dispatch(__CLASS__
, 'hasInstalledExtensions', [$extensionKey]);
441 * Get the SignalSlot dispatcher
445 protected function getSignalSlotDispatcher()
447 if (!isset($this->signalSlotDispatcher
)) {
448 $this->signalSlotDispatcher
= GeneralUtility
::makeInstance(ObjectManager
::class)
449 ->get(Dispatcher
::class);
451 return $this->signalSlotDispatcher
;