[BUGFIX] Fix dependency handling of EM
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Classes / Utility / DependencyUtility.php
1 <?php
2 namespace TYPO3\CMS\Extensionmanager\Utility;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2012 Susanne Moog <susanne.moog@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Utility for dealing with dependencies
31 *
32 * @author Susanne Moog <susanne.moog@typo3.org>
33 * @package Extension Manager
34 * @subpackage Utility
35 */
36 class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface {
37
38 /**
39 * @var \TYPO3\CMS\Extbase\Object\ObjectManager
40 */
41 protected $objectManager;
42
43 /**
44 * @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
45 */
46 protected $extensionRepository;
47
48 /**
49 * @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
50 */
51 protected $listUtility;
52
53 /**
54 * @var \TYPO3\CMS\Extensionmanager\Utility\EmConfUtility
55 */
56 protected $emConfUtility;
57
58 /**
59 * @var \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService
60 */
61 protected $managementService;
62
63 /**
64 * @var array
65 */
66 protected $availableExtensions = array();
67
68 /**
69 * @var array
70 */
71 protected $errors = array();
72
73 /**
74 * @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
75 * @return void
76 */
77 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager) {
78 $this->objectManager = $objectManager;
79 }
80
81 /**
82 * @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository
83 * @return void
84 */
85 public function injectExtensionRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository) {
86 $this->extensionRepository = $extensionRepository;
87 }
88
89 /**
90 * @param \TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility
91 * @return void
92 */
93 public function injectListUtility(\TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility) {
94 $this->listUtility = $listUtility;
95 }
96
97 /**
98 * @param \TYPO3\CMS\Extensionmanager\Utility\EmConfUtility $emConfUtility
99 * @return void
100 */
101 public function injectEmConfUtility(\TYPO3\CMS\Extensionmanager\Utility\EmConfUtility $emConfUtility) {
102 $this->emConfUtility = $emConfUtility;
103 }
104
105 /**
106 * @param \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService
107 * @return void
108 */
109 public function injectManagementService(\TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService) {
110 $this->managementService = $managementService;
111 }
112
113 /**
114 * Setter for available extensions
115 * gets available extensions from list utility if not already done
116 *
117 * @return void
118 */
119 protected function setAvailableExtensions() {
120 $this->availableExtensions = $this->listUtility->getAvailableExtensions();
121 }
122
123 /**
124 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
125 * @return void
126 */
127 public function buildExtensionDependenciesTree($extension) {
128 $dependencies = $extension->getDependencies();
129 $this->checkDependencies($dependencies);
130 }
131
132 /**
133 * @param string $dependencies
134 * @return \SplObjectStorage
135 */
136 public function convertDependenciesToObjects($dependencies) {
137 $unserializedDependencies = unserialize($dependencies);
138 $dependenciesObject = new \SplObjectStorage();
139 foreach ($unserializedDependencies as $dependencyType => $dependencyValues) {
140 foreach ($dependencyValues as $dependency => $versions) {
141 if ($dependencyType && $dependency) {
142 $versionNumbers = \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionsStringToVersionNumbers($versions);
143 $lowest = $versionNumbers[0];
144 if (count($versionNumbers) === 2) {
145 $highest = $versionNumbers[1];
146 } else {
147 $highest = '';
148 }
149 /** @var $dependencyObject \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency */
150 $dependencyObject = $this->objectManager->create('TYPO3\\CMS\\Extensionmanager\\Domain\\Model\\Dependency');
151 $dependencyObject->setType($dependencyType);
152 $dependencyObject->setIdentifier($dependency);
153 $dependencyObject->setLowestVersion($lowest);
154 $dependencyObject->setHighestVersion($highest);
155 $dependenciesObject->attach($dependencyObject);
156 unset($dependencyObject);
157 }
158 }
159 }
160 return $dependenciesObject;
161 }
162
163 /**
164 * Checks dependencies for special cases (currently typo3 and php)
165 *
166 * @param \SplObjectStorage $dependencies
167 * @return boolean
168 */
169 protected function checkDependencies(\SplObjectStorage $dependencies) {
170 $dependenciesToResolve = FALSE;
171 foreach ($dependencies as $dependency) {
172 $identifier = strtolower($dependency->getIdentifier());
173 if (in_array($identifier, \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency::$specialDependencies)) {
174 $methodname = 'check' . ucfirst($identifier) . 'Dependency';
175 try {
176 $this->{$methodname}($dependency);
177 } catch (\TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException $e) {
178 $this->errors[] = array(
179 'identifier' => $identifier,
180 'message' => $e->getMessage()
181 );
182 }
183 } else {
184 if ($dependency->getType() === 'depends') {
185 $dependenciesToResolve = !(bool) $this->checkExtensionDependency($dependency);
186 }
187 }
188 }
189 return $dependenciesToResolve;
190 }
191
192 /**
193 * Returns true if current TYPO3 version fulfills extension requirements
194 *
195 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
196 * @return boolean
197 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
198 */
199 protected function checkTypo3Dependency(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
200 $lowerCaseIdentifier = strtolower($dependency->getIdentifier());
201 if ($lowerCaseIdentifier === 'typo3') {
202 if (!($dependency->getLowestVersion() === '') && version_compare(\TYPO3\CMS\Core\Utility\VersionNumberUtility::getNumericTypo3Version(), $dependency->getLowestVersion()) === -1) {
203 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Your TYPO3 version is lower than necessary. You need at least TYPO3 version ' . $dependency->getLowestVersion());
204 }
205 if (!($dependency->getHighestVersion() === '') && version_compare($dependency->getHighestVersion(), \TYPO3\CMS\Core\Utility\VersionNumberUtility::getNumericTypo3Version()) === -1) {
206 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Your TYPO3 version is higher than allowed. You can use TYPO3 versions ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion());
207 }
208 } else {
209 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('checkTypo3Dependency can only check TYPO3 dependencies. Found dependency with identifier "' . $dependency->getIdentifier() . '"');
210 }
211 return TRUE;
212 }
213
214 /**
215 * Returns true if current php version fulfills extension requirements
216 *
217 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
218 * @return boolean
219 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
220 */
221 protected function checkPhpDependency(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
222 $lowerCaseIdentifier = strtolower($dependency->getIdentifier());
223 if ($lowerCaseIdentifier === 'php') {
224 if (!($dependency->getLowestVersion() === '') && version_compare(PHP_VERSION, $dependency->getLowestVersion()) === -1) {
225 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Your PHP version is lower than necessary. You need at least PHP version ' . $dependency->getLowestVersion());
226 }
227 if (!($dependency->getHighestVersion() === '') && version_compare($dependency->getHighestVersion(), PHP_VERSION) === -1) {
228 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Your PHP version is higher than allowed. You can use PHP versions ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion());
229 }
230 } else {
231 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('checkPhpDependency can only check PHP dependencies. Found dependency with identifier "' . $dependency->getIdentifier() . '"');
232 }
233 return TRUE;
234 }
235
236 /**
237 * Main controlling function for checking dependencies
238 * Dependency check is done in the following way:
239 * - installed extension in matching version ? - return true
240 * - available extension in matching version ? - mark for installation
241 * - remote (TER) extension in matching version? - mark for download
242 *
243 * @todo handle exceptions / markForUpload
244 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
245 * @return boolean
246 */
247 protected function checkExtensionDependency(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
248 $extensionKey = $dependency->getIdentifier();
249 $extensionIsLoaded = $this->isDependentExtensionLoaded($extensionKey);
250 if ($extensionIsLoaded === TRUE) {
251 $isLoadedVersionCompatible = $this->isLoadedVersionCompatible($dependency);
252 if ($isLoadedVersionCompatible === TRUE) {
253 return TRUE;
254 } else {
255 $this->getExtensionFromTer($extensionKey, $dependency);
256 }
257 } else {
258 $extensionIsAvailable = $this->isDependentExtensionAvailable($extensionKey);
259 if ($extensionIsAvailable === TRUE) {
260 $isAvailableVersionCompatible = $this->isAvailableVersionCompatible($dependency);
261 if ($isAvailableVersionCompatible) {
262 $this->managementService->markExtensionForInstallation($extensionKey);
263 } else {
264 $this->getExtensionFromTer($extensionKey, $dependency);
265 }
266 } else {
267 $this->getExtensionFromTer($extensionKey, $dependency);
268 }
269 }
270 return FALSE;
271 }
272
273 /**
274 * Handles checks to find a compatible extension version from TER
275 * to fulfill given dependency
276 *
277 * @todo unit tests
278 * @param string $extensionKey
279 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
280 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
281 * @return void
282 */
283 protected function getExtensionFromTer($extensionKey, \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
284 $isExtensionDownloadableFromTer = $this->isExtensionDownloadableFromTer($extensionKey);
285 if ($isExtensionDownloadableFromTer === TRUE) {
286 $isDownloadableVersionCompatible = $this->isDownloadableVersionCompatible($dependency);
287 if ($isDownloadableVersionCompatible === TRUE) {
288 $latestCompatibleExtensionByIntegerVersionDependency = $this->getLatestCompatibleExtensionByIntegerVersionDependency($dependency);
289 if ($latestCompatibleExtensionByIntegerVersionDependency instanceof \TYPO3\CMS\Extensionmanager\Domain\Model\Extension) {
290 if ($this->isDependentExtensionLoaded($extensionKey)) {
291 $this->managementService->markExtensionForUpdate($latestCompatibleExtensionByIntegerVersionDependency);
292 } else {
293 $this->managementService->markExtensionForDownload($latestCompatibleExtensionByIntegerVersionDependency);
294 }
295 } else {
296 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Could not resolve dependency for "' . $dependency->getIdentifier() . '"');
297 }
298 } else {
299 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('No compatible version found for extension ' . $extensionKey);
300 }
301 } else {
302 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('The extension ' . $extensionKey . ' is not available from TER.');
303 }
304 }
305
306 /**
307 * @param string $extensionKey
308 * @return bool
309 */
310 protected function isDependentExtensionLoaded($extensionKey) {
311 return \TYPO3\CMS\Core\Extension\ExtensionManager::isLoaded($extensionKey);
312 }
313
314 /**
315 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
316 * @return boolean
317 */
318 protected function isLoadedVersionCompatible(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
319 $extensionVersion = \TYPO3\CMS\Core\Extension\ExtensionManager::getExtensionVersion($dependency->getIdentifier());
320 return $this->isVersionCompatible($extensionVersion, $dependency);
321 }
322
323 /**
324 * @param string $version
325 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
326 * @return boolean
327 */
328 protected function isVersionCompatible($version, \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
329 if (!($dependency->getLowestVersion() === '') && version_compare($version, $dependency->getLowestVersion()) === -1) {
330 return FALSE;
331 }
332 if (!($dependency->getHighestVersion() === '') && version_compare($dependency->getHighestVersion(), $version) === -1) {
333 return FALSE;
334 }
335 return TRUE;
336 }
337
338 /**
339 * Checks whether the needed extension is available
340 * (not necessarily installed, but present in system)
341 *
342 * @param string $extensionKey
343 * @return boolean
344 */
345 protected function isDependentExtensionAvailable($extensionKey) {
346 $this->setAvailableExtensions();
347 return array_key_exists($extensionKey, $this->availableExtensions);
348 }
349
350 /**
351 * Checks whether the available version is compatible
352 *
353 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
354 * @return boolean
355 */
356 protected function isAvailableVersionCompatible(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
357 $this->setAvailableExtensions();
358 $extensionData = $this->emConfUtility->includeEmConf($this->availableExtensions[$dependency->getIdentifier()]);
359 return $this->isVersionCompatible($extensionData['version'], $dependency);
360 }
361
362 /**
363 * Checks whether a ter extension with $extensionKey exists
364 *
365 * @param string $extensionKey
366 * @return boolean
367 */
368 protected function isExtensionDownloadableFromTer($extensionKey) {
369 return $this->extensionRepository->countByExtensionKey($extensionKey) > 0;
370 }
371
372 /**
373 * Checks whether a compatible version of the extension exists in TER
374 *
375 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
376 * @return boolean
377 */
378 protected function isDownloadableVersionCompatible(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
379 $versions = $this->getLowestAndHighestIntegerVersions($dependency);
380 return count($this->extensionRepository->countByVersionRangeAndExtensionKey($dependency->getIdentifier(), $versions['lowestIntegerVersion'], $versions['highestIntegerVersion'])) > 0;
381 }
382
383 /**
384 * Get the latest compatible version of an extension that
385 * fulfills the given dependency from TER
386 *
387 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
388 * @return \TYPO3\CMS\Extensionmanager\Domain\Model\Extension
389 */
390 protected function getLatestCompatibleExtensionByIntegerVersionDependency(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
391 $versions = $this->getLowestAndHighestIntegerVersions($dependency);
392 $compatibleDataSets = $this->extensionRepository->findByVersionRangeAndExtensionKeyOrderedByVersion($dependency->getIdentifier(), $versions['lowestIntegerVersion'], $versions['highestIntegerVersion']);
393 return $compatibleDataSets->getFirst();
394 }
395
396 /**
397 * Return array of lowest and highest version of dependency as integer
398 *
399 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency
400 * @return array
401 */
402 protected function getLowestAndHighestIntegerVersions(\TYPO3\CMS\Extensionmanager\Domain\Model\Dependency $dependency) {
403 $lowestVersion = $dependency->getLowestVersion();
404 $lowestVersionInteger = $lowestVersion ? \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger($lowestVersion) : 0;
405 $highestVersion = $dependency->getHighestVersion();
406 $highestVersionInteger = $highestVersion ? \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger($highestVersion) : 0;
407 return array(
408 'lowestIntegerVersion' => $lowestVersionInteger,
409 'highestIntegerVersion' => $highestVersionInteger
410 );
411 }
412
413 public function findInstalledExtensionsThatDependOnMe($extensionKey) {
414 $availableExtensions = $this->listUtility->getAvailableExtensions();
415 $availableAndInstalledExtensions = $this->listUtility->getAvailableAndInstalledExtensions($availableExtensions);
416 $availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfAndTerInformation($availableAndInstalledExtensions);
417 $dependentExtensions = array();
418 foreach ($availableAndInstalledExtensions as $availableAndInstalledExtensionKey => $availableAndInstalledExtension) {
419 if (isset($availableAndInstalledExtension['installed']) && $availableAndInstalledExtension['installed'] === TRUE) {
420 if (is_array($availableAndInstalledExtension['constraints']) && is_array($availableAndInstalledExtension['constraints']['depends']) && array_key_exists($extensionKey, $availableAndInstalledExtension['constraints']['depends'])) {
421 $dependentExtensions[] = $availableAndInstalledExtensionKey;
422 }
423 }
424 }
425 return $dependentExtensions;
426 }
427
428 }
429
430
431 ?>