[BUGFIX] Cannot uninstall packages with dependencies in install tool
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Package / PackageManager.php
1 <?php
2 namespace TYPO3\CMS\Core\Package;
3
4 /* *
5 * This script belongs to the TYPO3 Flow framework. *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13
14 use TYPO3\Flow\Annotations as Flow;
15
16 /**
17 * The default TYPO3 Package Manager
18 * Adapted from FLOW for TYPO3 CMS
19 *
20 * @api
21 * @Flow\Scope("singleton")
22 */
23 class PackageManager extends \TYPO3\Flow\Package\PackageManager implements \TYPO3\CMS\Core\SingletonInterface {
24
25 /**
26 * @var \TYPO3\CMS\Core\Core\ClassLoader
27 */
28 protected $classLoader;
29
30 /**
31 * @var \TYPO3\CMS\Core\Package\DependencyResolver
32 */
33 protected $dependencyResolver;
34
35 /**
36 * @var \TYPO3\CMS\Core\Core\Bootstrap
37 */
38 protected $bootstrap;
39
40 /**
41 * @var \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend
42 */
43 protected $coreCache;
44
45 /**
46 * @var string
47 */
48 protected $cacheIdentifier;
49
50 /**
51 * @var array
52 */
53 protected $extAutoloadClassFiles;
54
55 /**
56 * @var array
57 */
58 protected $packagesBasePaths = array();
59
60 /**
61 * @var array
62 */
63 protected $packageAliasMap = array();
64
65 /**
66 * @var array
67 */
68 protected $requiredPackageKeys = array();
69
70 /**
71 * @var array
72 */
73 protected $runtimeActivatedPackages = array();
74
75 /**
76 * Absolute path leading to the various package directories
77 * @var string
78 */
79 protected $packagesBasePath = PATH_site;
80
81 /**
82 * Constructor
83 */
84 public function __construct() {
85 $this->packagesBasePaths = array(
86 'local' => PATH_typo3conf . 'ext',
87 'global' => PATH_typo3 . 'ext',
88 'sysext' => PATH_typo3 . 'sysext',
89 'composer' => PATH_site . 'Packages',
90 );
91 $this->packageStatesPathAndFilename = PATH_typo3conf . 'PackageStates.php';
92 $this->packageFactory = new PackageFactory($this);
93 }
94
95 /**
96 * @param \TYPO3\CMS\Core\Core\ClassLoader $classLoader
97 */
98 public function injectClassLoader(\TYPO3\CMS\Core\Core\ClassLoader $classLoader) {
99 $this->classLoader = $classLoader;
100 }
101
102 /**
103 * @param \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache
104 */
105 public function injectCoreCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache) {
106 $this->coreCache = $coreCache;
107 }
108
109 /**
110 * @param DependencyResolver
111 */
112 public function injectDependencyResolver(DependencyResolver $dependencyResolver) {
113 $this->dependencyResolver = $dependencyResolver;
114 }
115
116 /**
117 * Initializes the package manager
118 *
119 * @param \TYPO3\CMS\Core\Core\Bootstrap|\TYPO3\Flow\Core\Bootstrap $bootstrap The current bootstrap; Flow Bootstrap is here by intention to keep the PackageManager valid to the interface
120 * @return void
121 */
122 public function initialize(\TYPO3\Flow\Core\Bootstrap $bootstrap) {
123 $this->bootstrap = $bootstrap;
124
125 $loadedFromCache = FALSE;
126 try {
127 $this->loadPackageManagerStatesFromCache();
128 $loadedFromCache = TRUE;
129 } catch (Exception\PackageManagerCacheUnavailableException $exception) {
130 $this->loadPackageStates();
131 $this->initializePackageObjects();
132 $this->initializeCompatibilityLoadedExtArray();
133 }
134
135 $cacheIdentifier = $this->getCacheIdentifier();
136 if ($cacheIdentifier === NULL) {
137 // Create an artificial cache identifier if the package states file is not available yet
138 // in order that the class loader and class alias map can cache anyways.
139 $cacheIdentifier = md5(implode('###', array_keys($this->activePackages)));
140 }
141 $this->classLoader->setCacheIdentifier($cacheIdentifier)->setPackages($this->activePackages);
142
143 foreach ($this->activePackages as $package) {
144 /** @var $package Package */
145 $package->boot($bootstrap);
146 }
147
148 if (!$loadedFromCache) {
149 $this->saveToPackageCache();
150 }
151 }
152
153 /**
154 * Updates the class loader with currently active packages.
155 * This method is currently a slot that monitors the after
156 * extension is installed signal to make the class loader
157 * populate its caches again.
158 * Maybe we find a better solution in the future, but as of now
159 * we have to do this as all caches are flushed after an extension
160 * is installed and the current request might fail otherwise.
161 */
162 public function updatePackagesForClassLoader() {
163 $this->classLoader->setPackages($this->activePackages);
164 }
165
166 /**
167 * @return PackageFactory
168 */
169 protected function getPackageFactory() {
170 if (!isset($this->packageFactory)) {
171 $this->packageFactory = new PackageFactory($this);
172 }
173 return $this->packageFactory;
174 }
175
176 /**
177 * @return string
178 */
179 protected function getCacheIdentifier() {
180 if ($this->cacheIdentifier === NULL) {
181 $mTime = @filemtime($this->packageStatesPathAndFilename);
182 if ($mTime !== FALSE) {
183 $this->cacheIdentifier = md5($this->packageStatesPathAndFilename . $mTime);
184 } else {
185 $this->cacheIdentifier = NULL;
186 }
187 }
188 return $this->cacheIdentifier;
189 }
190
191 /**
192 * @return string
193 */
194 protected function getCacheEntryIdentifier() {
195 $cacheIdentifier = $this->getCacheIdentifier();
196 return $cacheIdentifier !== NULL ? 'PackageManager_' . $cacheIdentifier : NULL;
197 }
198
199 /**
200 *
201 */
202 protected function saveToPackageCache() {
203 $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
204 if ($cacheEntryIdentifier !== NULL && !$this->coreCache->has($cacheEntryIdentifier)) {
205 // Package objects get their own cache entry, so PHP does not have to parse the serialized string
206 $packageObjectsCacheEntryIdentifier = str_replace('.', '', uniqid('PackageObjects_', TRUE));
207 // Build cache file
208 $packageCache = array(
209 'packageStatesConfiguration' => $this->packageStatesConfiguration,
210 'packageAliasMap' => $this->packageAliasMap,
211 'packageKeys' => $this->packageKeys,
212 'activePackageKeys' => array_keys($this->activePackages),
213 'requiredPackageKeys' => $this->requiredPackageKeys,
214 'loadedExtArray' => $GLOBALS['TYPO3_LOADED_EXT'],
215 'packageObjectsCacheEntryIdentifier' => $packageObjectsCacheEntryIdentifier
216 );
217 $packageClassSources = array(
218 'typo3\\flow\\package\\package' => NULL,
219 'typo3\\cms\\core\\package\\package' => NULL,
220 );
221 foreach ($this->packages as $package) {
222 $packageClassName = strtolower(get_class($package));
223 if (!isset($packageClassSources[$packageClassName]) || $packageClassSources[$packageClassName] === NULL) {
224 $reflectionPackageClass = new \ReflectionClass($packageClassName);
225 $packageClassSource = file_get_contents($reflectionPackageClass->getFileName());
226 $packageClassSources[$packageClassName] = preg_replace('/<\?php|\?>/i', '', $packageClassSource);
227 }
228 }
229 $this->coreCache->set($packageObjectsCacheEntryIdentifier, serialize($this->packages));
230 $this->coreCache->set(
231 $cacheEntryIdentifier,
232 implode(PHP_EOL, $packageClassSources) . PHP_EOL .
233 'return ' . PHP_EOL . var_export($packageCache, TRUE) . ';'
234 );
235 }
236 }
237
238 /**
239 * Attempts to load the package manager states from cache
240 *
241 * @throws Exception\PackageManagerCacheUnavailableException
242 */
243 protected function loadPackageManagerStatesFromCache() {
244 $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
245 if ($cacheEntryIdentifier === NULL || !$this->coreCache->has($cacheEntryIdentifier) || !($packageCache = $this->coreCache->requireOnce($cacheEntryIdentifier))) {
246 throw new Exception\PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883342);
247 }
248 $this->packageStatesConfiguration = $packageCache['packageStatesConfiguration'];
249 $this->packageAliasMap = $packageCache['packageAliasMap'];
250 $this->packageKeys = $packageCache['packageKeys'];
251 $this->requiredPackageKeys = $packageCache['requiredPackageKeys'];
252 $GLOBALS['TYPO3_LOADED_EXT'] = $packageCache['loadedExtArray'];
253 $GLOBALS['TYPO3_currentPackageManager'] = $this;
254 // Strip off PHP Tags from Php Cache Frontend
255 $packageObjects = substr(substr($this->coreCache->get($packageCache['packageObjectsCacheEntryIdentifier']), 6), 0, -2);
256 $this->packages = unserialize($packageObjects);
257 foreach ($packageCache['activePackageKeys'] as $activePackageKey) {
258 $this->activePackages[$activePackageKey] = $this->packages[$activePackageKey];
259 }
260 unset($GLOBALS['TYPO3_currentPackageManager']);
261 }
262
263 /**
264 * Loads the states of available packages from the PackageStates.php file.
265 * The result is stored in $this->packageStatesConfiguration.
266 *
267 * @throws Exception\PackageStatesUnavailableException
268 * @return void
269 */
270 protected function loadPackageStates() {
271 $this->packageStatesConfiguration = @include($this->packageStatesPathAndFilename) ?: array();
272 if (!isset($this->packageStatesConfiguration['version']) || $this->packageStatesConfiguration['version'] < 4) {
273 $this->packageStatesConfiguration = array();
274 }
275 if ($this->packageStatesConfiguration !== array()) {
276 $this->registerPackagesFromConfiguration();
277 } else {
278 throw new Exception\PackageStatesUnavailableException('The PackageStates.php file is either corrupt or unavailable.', 1381507733);
279 }
280 }
281
282 /**
283 * Initializes activePackages and requiredPackageKeys properties
284 *
285 * Saves PackageStates.php if list of required extensions has changed.
286 *
287 * @return void
288 */
289 protected function initializePackageObjects() {
290 $requiredPackages = array();
291 foreach ($this->packages as $packageKey => $package) {
292 $protected = $package->isProtected();
293 if ($protected) {
294 $requiredPackages[$packageKey] = $package;
295 }
296 if (isset($this->packageStatesConfiguration['packages'][$packageKey]['state']) && $this->packageStatesConfiguration['packages'][$packageKey]['state'] === 'active') {
297 $this->activePackages[$packageKey] = $package;
298 }
299 }
300 $previousActivePackage = $this->activePackages;
301 $this->activePackages = array_merge($requiredPackages, $this->activePackages);
302 $this->requiredPackageKeys = array_keys($requiredPackages);
303
304 if ($this->activePackages != $previousActivePackage) {
305 foreach ($this->requiredPackageKeys as $requiredPackageKey) {
306 $this->packageStatesConfiguration['packages'][$requiredPackageKey]['state'] = 'active';
307 }
308 $this->sortAndSavePackageStates();
309 }
310 }
311
312 /**
313 * Initializes a backwards compatibility $GLOBALS['TYPO3_LOADED_EXT'] array
314 *
315 * @return void
316 */
317 protected function initializeCompatibilityLoadedExtArray() {
318 $loadedExtObj = new \TYPO3\CMS\Core\Compatibility\LoadedExtensionsArray($this);
319 $GLOBALS['TYPO3_LOADED_EXT'] = $loadedExtObj->toArray();
320 }
321
322
323 /**
324 * Scans all directories in the packages directories for available packages.
325 * For each package a Package object is created and stored in $this->packages.
326 *
327 * @return void
328 * @throws \TYPO3\Flow\Package\Exception\DuplicatePackageException
329 */
330 public function scanAvailablePackages() {
331 $previousPackageStatesConfiguration = $this->packageStatesConfiguration;
332
333 if (isset($this->packageStatesConfiguration['packages'])) {
334 foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $configuration) {
335 if (!@file_exists($this->packagesBasePath . $configuration['packagePath'])) {
336 unset($this->packageStatesConfiguration['packages'][$packageKey]);
337 }
338 }
339 } else {
340 $this->packageStatesConfiguration['packages'] = array();
341 }
342
343 foreach ($this->packagesBasePaths as $key => $packagesBasePath) {
344 if (!is_dir($packagesBasePath)) {
345 unset($this->packagesBasePaths[$key]);
346 }
347 }
348
349 $packagePaths = $this->scanLegacyExtensions();
350 foreach ($this->packagesBasePaths as $packagesBasePath) {
351 $this->scanPackagesInPath($packagesBasePath, $packagePaths);
352 }
353
354 foreach ($packagePaths as $packagePath => $composerManifestPath) {
355 $packagesBasePath = PATH_site;
356 foreach ($this->packagesBasePaths as $basePath) {
357 if (strpos($packagePath, $basePath) === 0) {
358 $packagesBasePath = $basePath;
359 break;
360 }
361 }
362 try {
363 $composerManifest = self::getComposerManifest($composerManifestPath);
364 $packageKey = PackageFactory::getPackageKeyFromManifest($composerManifest, $packagePath, $packagesBasePath);
365 $this->composerNameToPackageKeyMap[strtolower($composerManifest->name)] = $packageKey;
366 $this->packageStatesConfiguration['packages'][$packageKey]['manifestPath'] = substr($composerManifestPath, strlen($packagePath)) ? : '';
367 $this->packageStatesConfiguration['packages'][$packageKey]['composerName'] = $composerManifest->name;
368 } catch (\TYPO3\Flow\Package\Exception\MissingPackageManifestException $exception) {
369 $relativePackagePath = substr($packagePath, strlen($packagesBasePath));
370 $packageKey = substr($relativePackagePath, strpos($relativePackagePath, '/') + 1, -1);
371 if (!$this->isPackageKeyValid($packageKey)) {
372 continue;
373 }
374 } catch (\TYPO3\Flow\Package\Exception\InvalidPackageKeyException $exception) {
375 continue;
376 }
377 if (!isset($this->packageStatesConfiguration['packages'][$packageKey]['state'])) {
378 $this->packageStatesConfiguration['packages'][$packageKey]['state'] = 'inactive';
379 }
380
381 $this->packageStatesConfiguration['packages'][$packageKey]['packagePath'] = str_replace($this->packagesBasePath, '', $packagePath);
382
383 // Change this to read the target from Composer or any other source
384 $this->packageStatesConfiguration['packages'][$packageKey]['classesPath'] = Package::DIRECTORY_CLASSES;
385 }
386
387 $registerOnlyNewPackages = !empty($this->packages);
388 $this->registerPackagesFromConfiguration($registerOnlyNewPackages);
389 if ($this->packageStatesConfiguration != $previousPackageStatesConfiguration) {
390 $this->sortAndsavePackageStates();
391 }
392 }
393
394 /**
395 * @param array $collectedExtensionPaths
396 * @return array
397 */
398 protected function scanLegacyExtensions(&$collectedExtensionPaths = array()) {
399 $legacyCmsPackageBasePathTypes = array('sysext', 'global', 'local');
400 foreach ($this->packagesBasePaths as $type => $packageBasePath) {
401 if (!in_array($type, $legacyCmsPackageBasePathTypes)) {
402 continue;
403 }
404 /** @var $fileInfo \SplFileInfo */
405 foreach (new \DirectoryIterator($packageBasePath) as $fileInfo) {
406 if (!$fileInfo->isDir()) {
407 continue;
408 }
409 $filename = $fileInfo->getFilename();
410 if ($filename[0] !== '.') {
411 $currentPath = \TYPO3\Flow\Utility\Files::getUnixStylePath($fileInfo->getPathName()) . '/';
412 if (file_exists($currentPath . 'ext_emconf.php')) {
413 $collectedExtensionPaths[$currentPath] = $currentPath;
414 }
415 }
416 }
417 }
418 return $collectedExtensionPaths;
419 }
420
421 /**
422 * Looks for composer.json in the given path and returns a path or NULL.
423 *
424 * @param string $packagePath
425 * @return array
426 */
427 protected function findComposerManifestPaths($packagePath) {
428 // If an ext_emconf.php file is found, we don't need to look deeper
429 if (file_exists($packagePath . '/ext_emconf.php')) {
430 return array();
431 }
432 return parent::findComposerManifestPaths($packagePath);
433 }
434
435 /**
436 * Requires and registers all packages which were defined in packageStatesConfiguration
437 *
438 * @param bool $registerOnlyNewPackages
439 * @return void
440 * @throws \TYPO3\Flow\Package\Exception\CorruptPackageException
441 */
442 protected function registerPackagesFromConfiguration($registerOnlyNewPackages = FALSE) {
443 foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $stateConfiguration) {
444
445 if ($registerOnlyNewPackages && $this->isPackageAvailable($packageKey)) {
446 continue;
447 }
448
449 $packagePath = isset($stateConfiguration['packagePath']) ? $stateConfiguration['packagePath'] : NULL;
450 $classesPath = isset($stateConfiguration['classesPath']) ? $stateConfiguration['classesPath'] : NULL;
451 $manifestPath = isset($stateConfiguration['manifestPath']) ? $stateConfiguration['manifestPath'] : NULL;
452
453 try {
454 $package = $this->getPackageFactory()->create($this->packagesBasePath, $packagePath, $packageKey, $classesPath, $manifestPath);
455 } catch (\TYPO3\Flow\Package\Exception\InvalidPackagePathException $exception) {
456 $this->unregisterPackageByPackageKey($packageKey);
457 continue;
458 } catch (\TYPO3\Flow\Package\Exception\InvalidPackageKeyException $exception) {
459 $this->unregisterPackageByPackageKey($packageKey);
460 continue;
461 }
462
463 $this->registerPackage($package, FALSE);
464
465 if (!$this->packages[$packageKey] instanceof \TYPO3\Flow\Package\PackageInterface) {
466 throw new \TYPO3\Flow\Package\Exception\CorruptPackageException(sprintf('The package class in package "%s" does not implement PackageInterface.', $packageKey), 1300782488);
467 }
468
469 $this->packageKeys[strtolower($packageKey)] = $packageKey;
470 if ($stateConfiguration['state'] === 'active') {
471 $this->activePackages[$packageKey] = $this->packages[$packageKey];
472 }
473 }
474 }
475
476 /**
477 * Register a native Flow package
478 *
479 * @param \TYPO3\Flow\Package\PackageInterface $package The Package to be registered
480 * @param bool $sortAndSave allows for not saving packagestates when used in loops etc.
481 * @return \TYPO3\Flow\Package\PackageInterface
482 * @throws \TYPO3\Flow\Package\Exception\CorruptPackageException
483 */
484 public function registerPackage(\TYPO3\Flow\Package\PackageInterface $package, $sortAndSave = TRUE) {
485 $package = parent::registerPackage($package, $sortAndSave);
486 if ($package instanceof PackageInterface) {
487 foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
488 $this->packageAliasMap[strtolower($packageToReplace)] = $package->getPackageKey();
489 }
490 }
491 return $package;
492 }
493
494 /**
495 * Unregisters a package from the list of available packages
496 *
497 * @param string $packageKey Package Key of the package to be unregistered
498 * @return void
499 */
500 protected function unregisterPackageByPackageKey($packageKey) {
501 try {
502 $package = $this->getPackage($packageKey);
503 if ($package instanceof PackageInterface) {
504 foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
505 unset($this->packageAliasMap[strtolower($packageToReplace)]);
506 }
507 $packageKey = $package->getPackageKey();
508 }
509 } catch (\TYPO3\Flow\Package\Exception\UnknownPackageException $e) {
510 }
511 parent::unregisterPackageByPackageKey($packageKey);
512 }
513
514 /**
515 * Resolves a Flow package key from a composer package name.
516 *
517 * @param string $composerName
518 * @return string
519 * @throws \TYPO3\Flow\Package\Exception\InvalidPackageStateException
520 */
521 public function getPackageKeyFromComposerName($composerName) {
522 if (isset($this->packageAliasMap[$composerName])) {
523 return $this->packageAliasMap[$composerName];
524 }
525 try {
526 return parent::getPackageKeyFromComposerName($composerName);
527 } catch (\TYPO3\Flow\Package\Exception\InvalidPackageStateException $exception) {
528 return $composerName;
529 }
530 }
531
532 /**
533 * @return array
534 */
535 public function getExtAutoloadRegistry() {
536 if (!isset($this->extAutoloadClassFiles)) {
537 $classRegistry = array();
538 foreach ($this->activePackages as $packageKey => $packageData) {
539 try {
540 $extensionAutoloadFile = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($packageKey, 'ext_autoload.php');
541 if (@file_exists($extensionAutoloadFile)) {
542 $classRegistry = array_merge($classRegistry, require $extensionAutoloadFile);
543 }
544 } catch (\BadFunctionCallException $e) {
545 }
546 }
547 $this->extAutoloadClassFiles = $classRegistry;
548 }
549 return $this->extAutoloadClassFiles;
550 }
551
552 /**
553 * Returns a PackageInterface object for the specified package.
554 * A package is available, if the package directory contains valid MetaData information.
555 *
556 * @param string $packageKey
557 * @return \TYPO3\Flow\Package\PackageInterface The requested package object
558 * @throws \TYPO3\Flow\Package\Exception\UnknownPackageException if the specified package is not known
559 * @api
560 */
561 public function getPackage($packageKey) {
562 if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
563 $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
564 }
565 return parent::getPackage($packageKey);
566 }
567
568 /**
569 * Returns TRUE if a package is available (the package's files exist in the packages directory)
570 * or FALSE if it's not. If a package is available it doesn't mean necessarily that it's active!
571 *
572 * @param string $packageKey The key of the package to check
573 * @return bool TRUE if the package is available, otherwise FALSE
574 * @api
575 */
576 public function isPackageAvailable($packageKey) {
577 if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
578 $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
579 }
580 return parent::isPackageAvailable($packageKey);
581 }
582
583 /**
584 * Returns TRUE if a package is activated or FALSE if it's not.
585 *
586 * @param string $packageKey The key of the package to check
587 * @return bool TRUE if package is active, otherwise FALSE
588 * @api
589 */
590 public function isPackageActive($packageKey) {
591 if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
592 $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
593 }
594 return isset($this->runtimeActivatedPackages[$packageKey]) || parent::isPackageActive($packageKey);
595 }
596
597 /**
598 * @param string $packageKey
599 */
600 public function deactivatePackage($packageKey) {
601 $this->sortAvailablePackagesByDependencies();
602
603 foreach ($this->packageStatesConfiguration['packages'] as $packageStateKey => $packageStateConfiguration) {
604 if ($packageKey === $packageStateKey || empty($packageStateConfiguration['dependencies']) || $packageStateConfiguration['state'] !== 'active') {
605 continue;
606 }
607 if (in_array($packageKey, $packageStateConfiguration['dependencies'])) {
608 $this->deactivatePackage($packageStateKey);
609 }
610 }
611
612 $package = $this->getPackage($packageKey);
613 parent::deactivatePackage($package->getPackageKey());
614 }
615
616 /**
617 * @param string $packageKey
618 */
619 public function activatePackage($packageKey) {
620 $package = $this->getPackage($packageKey);
621 parent::activatePackage($package->getPackageKey());
622 $this->classLoader->addActivePackage($package);
623 }
624
625 /**
626 * Enables packages during runtime, but no class aliases will be available
627 *
628 * @param string $packageKey
629 * @api
630 */
631 public function activatePackageDuringRuntime($packageKey) {
632 $package = $this->getPackage($packageKey);
633 $this->runtimeActivatedPackages[$package->getPackageKey()] = $package;
634 $this->classLoader->addActivePackage($package);
635 if (!isset($GLOBALS['TYPO3_LOADED_EXT'][$package->getPackageKey()])) {
636 $loadedExtArrayElement = new \TYPO3\CMS\Core\Compatibility\LoadedExtensionArrayElement($package);
637 $GLOBALS['TYPO3_LOADED_EXT'][$package->getPackageKey()] = $loadedExtArrayElement->toArray();
638 }
639 }
640
641
642 /**
643 * @param string $packageKey
644 */
645 public function deletePackage($packageKey) {
646 $package = $this->getPackage($packageKey);
647 parent::deletePackage($package->getPackageKey());
648 }
649
650
651 /**
652 * @param string $packageKey
653 */
654 public function freezePackage($packageKey) {
655 $package = $this->getPackage($packageKey);
656 parent::freezePackage($package->getPackageKey());
657 }
658
659 /**
660 * @param string $packageKey
661 * @return bool
662 */
663 public function isPackageFrozen($packageKey) {
664 $package = $this->getPackage($packageKey);
665 return parent::isPackageFrozen($package->getPackageKey());
666 }
667
668 /**
669 * @param string $packageKey
670 */
671 public function unfreezePackage($packageKey) {
672 $package = $this->getPackage($packageKey);
673 parent::unfreezePackage($package->getPackageKey());
674 }
675
676 /**
677 * @param string $packageKey
678 */
679 public function refreezePackage($packageKey) {
680 $package = $this->getPackage($packageKey);
681 parent::refreezePackage($package->getPackageKey());
682 }
683
684 /**
685 * Returns an array of \TYPO3\Flow\Package objects of all active packages.
686 * A package is active, if it is available and has been activated in the package
687 * manager settings. This method returns runtime activated packages too
688 *
689 * @return \TYPO3\Flow\Package\PackageInterface[]
690 * @api
691 */
692 public function getActivePackages() {
693 return array_merge(parent::getActivePackages(), $this->runtimeActivatedPackages);
694 }
695
696 /**
697 * Orders all packages by comparing their dependencies. By this, the packages
698 * and package configurations arrays holds all packages in the correct
699 * initialization order.
700 *
701 * @return void
702 */
703 protected function sortAvailablePackagesByDependencies() {
704 $this->resolvePackageDependencies();
705
706 $this->packageStatesConfiguration['packages'] = $this->dependencyResolver->sortPackageStatesConfigurationByDependency($this->packageStatesConfiguration['packages']);
707
708 // Reorder the packages according to the loading order
709 $newPackages = array();
710 foreach (array_keys($this->packageStatesConfiguration['packages']) as $packageKey) {
711 $newPackages[$packageKey] = $this->packages[$packageKey];
712 }
713 $this->packages = $newPackages;
714 }
715
716 /**
717 * Resolves the dependent packages from the meta data of all packages recursively. The
718 * resolved direct or indirect dependencies of each package will put into the package
719 * states configuration array.
720 *
721 * @return void
722 */
723 protected function resolvePackageDependencies() {
724 parent::resolvePackageDependencies();
725 foreach ($this->packages as $packageKey => $package) {
726 $this->packageStatesConfiguration['packages'][$packageKey]['suggestions'] = $this->getSuggestionArrayForPackage($packageKey);
727 }
728 }
729
730 /**
731 * Returns an array of suggested package keys for the given package.
732 *
733 * @param string $packageKey The package key to fetch the suggestions for
734 * @return array|NULL An array of directly suggested packages
735 */
736 protected function getSuggestionArrayForPackage($packageKey) {
737 if (!isset($this->packages[$packageKey])) {
738 return NULL;
739 }
740 $suggestedPackageKeys = array();
741 $suggestedPackageConstraints = $this->packages[$packageKey]->getPackageMetaData()->getConstraintsByType(\TYPO3\Flow\Package\MetaDataInterface::CONSTRAINT_TYPE_SUGGESTS);
742 foreach ($suggestedPackageConstraints as $constraint) {
743 if ($constraint instanceof \TYPO3\Flow\Package\MetaData\PackageConstraint) {
744 $suggestedPackageKey = $constraint->getValue();
745 if (isset($this->packages[$suggestedPackageKey])) {
746 $suggestedPackageKeys[] = $suggestedPackageKey;
747 }
748 }
749 }
750 return array_reverse($suggestedPackageKeys);
751 }
752
753 /**
754 * Saves the current content of $this->packageStatesConfiguration to the
755 * PackageStates.php file.
756 *
757 * @return void
758 */
759 protected function sortAndSavePackageStates() {
760 parent::sortAndSavePackageStates();
761
762 $this->initializeCompatibilityLoadedExtArray();
763 \TYPO3\CMS\Core\Utility\OpcodeCacheUtility::clearAllActive($this->packageStatesPathAndFilename);
764 }
765
766 /**
767 * Check the conformance of the given package key
768 *
769 * @param string $packageKey The package key to validate
770 * @return bool If the package key is valid, returns TRUE otherwise FALSE
771 * @api
772 */
773 public function isPackageKeyValid($packageKey) {
774 return parent::isPackageKeyValid($packageKey) || preg_match(\TYPO3\CMS\Core\Package\Package::PATTERN_MATCH_EXTENSIONKEY, $packageKey) === 1;
775 }
776 }