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