‪TYPO3CMS  ‪main
ExtensionManagementService.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
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 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
33 
37 class ExtensionManagementService implements SingletonInterface
38 {
39  protected DependencyUtility $dependencyUtility;
40  protected InstallUtility $installUtility;
41 
42  protected bool $automaticInstallationEnabled = true;
43  protected bool $skipDependencyCheck = false;
44 
45  public function __construct(
46  protected readonly RemoteRegistry $remoteRegistry,
47  protected readonly FileHandlingUtility $fileHandlingUtility,
48  protected readonly DownloadQueue $downloadQueue,
49  protected readonly EventDispatcherInterface $eventDispatcher
50  ) {}
51 
52  public function injectDependencyUtility(DependencyUtility $dependencyUtility)
53  {
54  $this->dependencyUtility = $dependencyUtility;
55  }
56 
57  public function injectInstallUtility(InstallUtility $installUtility)
58  {
59  $this->installUtility = $installUtility;
60  }
61 
62  public function markExtensionForInstallation(string $extensionKey): void
63  {
64  // We have to check for dependencies of the extension first, before marking it for installation
65  // because this extension might have dependencies, which need to be installed first
66  $this->installUtility->reloadAvailableExtensions();
67  $extension = $this->getExtension($extensionKey);
68  $this->dependencyUtility->checkDependencies($extension);
69  $this->downloadQueue->addExtensionToInstallQueue($extension);
70  }
71 
75  public function markExtensionForDownload(Extension $extension): void
76  {
77  // We have to check for dependencies of the extension first, before marking it for download
78  // because this extension might have dependencies, which need to be downloaded and installed first
79  $this->dependencyUtility->checkDependencies($extension);
80  if (!$this->dependencyUtility->hasDependencyErrors()) {
81  $this->downloadQueue->addExtensionToQueue($extension);
82  }
83  }
84 
85  public function markExtensionForUpdate(Extension $extension): void
86  {
87  // We have to check for dependencies of the extension first, before marking it for download
88  // because this extension might have dependencies, which need to be downloaded and installed first
89  $this->dependencyUtility->checkDependencies($extension);
90  $this->downloadQueue->addExtensionToQueue($extension, 'update');
91  }
92 
96  public function setSkipDependencyCheck(bool $skipDependencyCheck): void
97  {
98  $this->skipDependencyCheck = $skipDependencyCheck;
99  }
100 
101  public function setAutomaticInstallationEnabled(bool $automaticInstallationEnabled): void
102  {
103  $this->automaticInstallationEnabled = $automaticInstallationEnabled;
104  }
105 
115  public function installExtension(Extension $extension): array|false
116  {
117  $this->downloadMainExtension($extension);
118  if (!$this->checkDependencies($extension)) {
119  return false;
120  }
121 
122  $downloadedDependencies = [];
123  $updatedDependencies = [];
124  $installQueue = [];
125 
126  // First resolve all dependencies and the sub-dependencies until all queues are empty as new extensions might be
127  // added each time
128  // Extensions have to be installed in reverse order. Extensions which were added at last are dependencies of
129  // earlier ones and need to be available before
130  while (!$this->downloadQueue->isQueueEmpty('download')
131  || !$this->downloadQueue->isQueueEmpty('update')
132  ) {
133  $installQueue = array_merge($this->downloadQueue->resetExtensionInstallStorage(), $installQueue);
134  // Get download and update information
135  $queue = $this->downloadQueue->resetExtensionQueue();
136  if (!empty($queue['download'])) {
137  $downloadedDependencies = array_merge($downloadedDependencies, $this->downloadDependencies($queue['download']));
138  }
139  $installQueue = array_merge($this->downloadQueue->resetExtensionInstallStorage(), $installQueue);
140  if ($this->automaticInstallationEnabled) {
141  if (!empty($queue['update'])) {
142  $this->downloadDependencies($queue['update']);
143  $updatedDependencies = array_merge($updatedDependencies, $this->uninstallDependenciesToBeUpdated($queue['update']));
144  }
145  $installQueue = array_merge($this->downloadQueue->resetExtensionInstallStorage(), $installQueue);
146  }
147  }
148 
149  // If there were any dependency errors we have to abort here
150  if ($this->dependencyUtility->hasDependencyErrors()) {
151  return false;
152  }
153 
154  // Attach extension to install queue
155  $this->downloadQueue->addExtensionToInstallQueue($extension);
156  $installQueue += $this->downloadQueue->resetExtensionInstallStorage();
157  $installedDependencies = [];
158  if ($this->automaticInstallationEnabled) {
159  $installedDependencies = $this->installDependencies($installQueue);
160  }
161 
162  return array_merge($downloadedDependencies, $updatedDependencies, $installedDependencies);
163  }
164 
168  public function getDependencyErrors(): array
169  {
170  return $this->dependencyUtility->getDependencyErrors();
171  }
172 
173  public function getExtension(string $extensionKey): Extension
174  {
176  $this->installUtility->enrichExtensionWithDetails($extensionKey)
177  );
178  }
179 
183  public function isAvailable(string $extensionKey): bool
184  {
185  return $this->installUtility->isAvailable($extensionKey);
186  }
187 
188  public function reloadPackageInformation(string $extensionKey): void
189  {
190  $this->installUtility->reloadPackageInformation($extensionKey);
191  }
192 
198  protected function checkDependencies(Extension $extension): bool
199  {
200  $this->dependencyUtility->setSkipDependencyCheck($this->skipDependencyCheck);
201  $this->dependencyUtility->checkDependencies($extension);
202 
203  return !$this->dependencyUtility->hasDependencyErrors();
204  }
205 
213  protected function uninstallDependenciesToBeUpdated(array $updateQueue): array
214  {
215  $resolvedDependencies = [];
216  foreach ($updateQueue as $extensionToUpdate) {
217  $this->installUtility->uninstall($extensionToUpdate->getExtensionKey());
218  $resolvedDependencies['updated'][$extensionToUpdate->getExtensionKey()] = $extensionToUpdate;
219  }
220  return $resolvedDependencies;
221  }
222 
228  protected function installDependencies(array $installQueue): array
229  {
230  if (empty($installQueue)) {
231  return [];
232  }
233  $this->eventDispatcher->dispatch(new BeforePackageActivationEvent($installQueue));
234  $resolvedDependencies = [];
235  $extensionKeys = array_keys($installQueue);
236  $this->installUtility->install(...$extensionKeys);
237  foreach ($extensionKeys as $extensionKey) {
238  if (!isset($resolvedDependencies['installed']) || !is_array($resolvedDependencies['installed'])) {
239  $resolvedDependencies['installed'] = [];
240  }
241  $resolvedDependencies['installed'][$extensionKey] = $extensionKey;
242  }
243  return $resolvedDependencies;
244  }
245 
253  protected function downloadDependencies(array $downloadQueue): array
254  {
255  $resolvedDependencies = [];
256  foreach ($downloadQueue as $extensionToDownload) {
257  $this->rawDownload($extensionToDownload);
258  $this->downloadQueue->removeExtensionFromQueue($extensionToDownload);
259  $resolvedDependencies['downloaded'][$extensionToDownload->getExtensionKey()] = $extensionToDownload;
260  $this->markExtensionForInstallation($extensionToDownload->getExtensionKey());
261  }
262  return $resolvedDependencies;
263  }
264 
268  public function getAndResolveDependencies(Extension $extension): array
269  {
270  $this->dependencyUtility->setSkipDependencyCheck($this->skipDependencyCheck);
271  $this->dependencyUtility->checkDependencies($extension);
272  $installQueue = $this->downloadQueue->getExtensionInstallStorage();
273  if ($installQueue !== []) {
274  $installQueue = ['install' => $installQueue];
275  }
276  return array_merge($this->downloadQueue->getExtensionQueue(), $installQueue);
277  }
278 
284  public function downloadMainExtension(Extension $extension): void
285  {
286  // The extension object has a uid if the extension is not present in the system
287  // or an update of a present extension is triggered.
288  if ($extension->getUid()) {
289  $this->rawDownload($extension);
290  }
291  }
292 
293  protected function rawDownload(Extension $extension): void
294  {
295  if (
297  || (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode')
298  ) {
299  throw new ExtensionManagerException('Extension Manager is in offline mode. No TER connection available.', 1437078620);
300  }
301 
302  $remoteIdentifier = $extension->getRemoteIdentifier();
303 
304  if ($this->remoteRegistry->hasRemote($remoteIdentifier)) {
305  $this->remoteRegistry
306  ->getRemote($remoteIdentifier)
307  ->downloadExtension(
308  $extension->getExtensionKey(),
309  $extension->getVersion(),
310  $this->fileHandlingUtility,
311  $extension->getMd5hash()
312  );
313  }
314  }
315 }
‪TYPO3\CMS\Extensionmanager\Domain\Model\Extension
Definition: Extension.php:30
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration
Definition: ExtensionConfiguration.php:47
‪TYPO3\CMS\Core\Core\Environment\isComposerMode
‪static isComposerMode()
Definition: Environment.php:137
‪TYPO3\CMS\Extensionmanager\Domain\Model\DownloadQueue
Definition: DownloadQueue.php:28
‪TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
Definition: FileHandlingUtility.php:40
‪TYPO3\CMS\Extensionmanager\Domain\Model\Extension\createFromExtensionArray
‪static createFromExtensionArray(array $extensionArray)
Definition: Extension.php:418
‪TYPO3\CMS\Extensionmanager\Service
Definition: ComposerManifestProposalGenerator.php:18
‪TYPO3\CMS\Extensionmanager\Utility\InstallUtility
Definition: InstallUtility.php:40
‪TYPO3\CMS\Extensionmanager\Utility\DependencyUtility
Definition: DependencyUtility.php:37
‪TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
Definition: ExtensionManagerException.php:25
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Package\Event\BeforePackageActivationEvent
Definition: BeforePackageActivationEvent.php:24
‪TYPO3\CMS\Extensionmanager\Remote\RemoteRegistry
Definition: RemoteRegistry.php:26