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