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