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