[BUGFIX] Fix loading order for extensions
[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 'sysext' => PATH_typo3 . 'sysext',
87 'global' => PATH_typo3 . 'ext',
88 'local' => PATH_typo3conf . 'ext',
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 // An extension might be overwritten by another one. (eg sysext overwritten with ext)
388 // We have to remember which paths we found the extension in as
389 // we need to know this in the DependencyResolver later.
390 $this->packageStatesConfiguration['packages'][$packageKey]['packagePathStack'][] = $this->packageStatesConfiguration['packages'][$packageKey]['packagePath'];
391
392 // Change this to read the target from Composer or any other source
393 $this->packageStatesConfiguration['packages'][$packageKey]['classesPath'] = Package::DIRECTORY_CLASSES;
394 }
395
396 $registerOnlyNewPackages = !empty($this->packages);
397 $this->registerPackagesFromConfiguration($registerOnlyNewPackages);
398 if ($this->packageStatesConfiguration != $previousPackageStatesConfiguration) {
399 $this->sortAndsavePackageStates();
400 }
401 }
402
403 /**
404 * @param array $collectedExtensionPaths
405 * @return array
406 */
407 protected function scanLegacyExtensions(&$collectedExtensionPaths = array()) {
408 $legacyCmsPackageBasePathTypes = array('sysext', 'global', 'local');
409 foreach ($this->packagesBasePaths as $type => $packageBasePath) {
410 if (!in_array($type, $legacyCmsPackageBasePathTypes)) {
411 continue;
412 }
413 /** @var $fileInfo \SplFileInfo */
414 foreach (new \DirectoryIterator($packageBasePath) as $fileInfo) {
415 if (!$fileInfo->isDir()) {
416 continue;
417 }
418 $filename = $fileInfo->getFilename();
419 if ($filename[0] !== '.') {
420 $currentPath = \TYPO3\Flow\Utility\Files::getUnixStylePath($fileInfo->getPathName()) . '/';
421 if (file_exists($currentPath . 'ext_emconf.php')) {
422 $collectedExtensionPaths[$currentPath] = $currentPath;
423 }
424 }
425 }
426 }
427 return $collectedExtensionPaths;
428 }
429
430 /**
431 * Looks for composer.json in the given path and returns a path or NULL.
432 *
433 * @param string $packagePath
434 * @return array
435 */
436 protected function findComposerManifestPaths($packagePath) {
437 // If an ext_emconf.php file is found, we don't need to look deeper
438 if (file_exists($packagePath . '/ext_emconf.php')) {
439 return array();
440 }
441 return parent::findComposerManifestPaths($packagePath);
442 }
443
444 /**
445 * Requires and registers all packages which were defined in packageStatesConfiguration
446 *
447 * @param boolean $registerOnlyNewPackages
448 * @return void
449 * @throws \TYPO3\Flow\Package\Exception\CorruptPackageException
450 */
451 protected function registerPackagesFromConfiguration($registerOnlyNewPackages = FALSE) {
452 foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $stateConfiguration) {
453
454 if ($registerOnlyNewPackages && $this->isPackageAvailable($packageKey)) {
455 continue;
456 }
457
458 $packagePath = isset($stateConfiguration['packagePath']) ? $stateConfiguration['packagePath'] : NULL;
459 $classesPath = isset($stateConfiguration['classesPath']) ? $stateConfiguration['classesPath'] : NULL;
460 $manifestPath = isset($stateConfiguration['manifestPath']) ? $stateConfiguration['manifestPath'] : NULL;
461
462 try {
463 $package = $this->getPackageFactory()->create($this->packagesBasePath, $packagePath, $packageKey, $classesPath, $manifestPath);
464 } catch (\TYPO3\Flow\Package\Exception\InvalidPackagePathException $exception) {
465 $this->unregisterPackageByPackageKey($packageKey);
466 continue;
467 } catch (\TYPO3\Flow\Package\Exception\InvalidPackageKeyException $exception) {
468 $this->unregisterPackageByPackageKey($packageKey);
469 continue;
470 }
471
472 $this->registerPackage($package, FALSE);
473
474 if (!$this->packages[$packageKey] instanceof \TYPO3\Flow\Package\PackageInterface) {
475 throw new \TYPO3\Flow\Package\Exception\CorruptPackageException(sprintf('The package class in package "%s" does not implement PackageInterface.', $packageKey), 1300782488);
476 }
477
478 $this->packageKeys[strtolower($packageKey)] = $packageKey;
479 if ($stateConfiguration['state'] === 'active') {
480 $this->activePackages[$packageKey] = $this->packages[$packageKey];
481 }
482 }
483 }
484
485 /**
486 * Register a native Flow package
487 *
488 * @param \TYPO3\Flow\Package\PackageInterface $package The Package to be registered
489 * @param boolean $sortAndSave allows for not saving packagestates when used in loops etc.
490 * @return \TYPO3\Flow\Package\PackageInterface
491 * @throws \TYPO3\Flow\Package\Exception\CorruptPackageException
492 */
493 public function registerPackage(\TYPO3\Flow\Package\PackageInterface $package, $sortAndSave = TRUE) {
494 $package = parent::registerPackage($package, $sortAndSave);
495 if ($package instanceof PackageInterface) {
496 foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
497 $this->packageAliasMap[strtolower($packageToReplace)] = $package->getPackageKey();
498 }
499 }
500 return $package;
501 }
502
503 /**
504 * Unregisters a package from the list of available packages
505 *
506 * @param string $packageKey Package Key of the package to be unregistered
507 * @return void
508 */
509 protected function unregisterPackageByPackageKey($packageKey) {
510 try {
511 $package = $this->getPackage($packageKey);
512 if ($package instanceof PackageInterface) {
513 foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
514 unset($this->packageAliasMap[strtolower($packageToReplace)]);
515 }
516 $packageKey = $package->getPackageKey();
517 }
518 } catch (\TYPO3\Flow\Package\Exception\UnknownPackageException $e) {
519 }
520 parent::unregisterPackageByPackageKey($packageKey);
521 }
522
523 /**
524 * Resolves a Flow package key from a composer package name.
525 *
526 * @param string $composerName
527 * @return string
528 * @throws \TYPO3\Flow\Package\Exception\InvalidPackageStateException
529 */
530 public function getPackageKeyFromComposerName($composerName) {
531 if (isset($this->packageAliasMap[$composerName])) {
532 return $this->packageAliasMap[$composerName];
533 }
534 try {
535 return parent::getPackageKeyFromComposerName($composerName);
536 } catch (\TYPO3\Flow\Package\Exception\InvalidPackageStateException $exception) {
537 return $composerName;
538 }
539 }
540
541 /**
542 * @return array
543 */
544 public function getExtAutoloadRegistry() {
545 if (!isset($this->extAutoloadClassFiles)) {
546 $classRegistry = array();
547 foreach ($this->activePackages as $packageKey => $packageData) {
548 try {
549 $extensionAutoloadFile = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($packageKey, 'ext_autoload.php');
550 if (@file_exists($extensionAutoloadFile)) {
551 $classRegistry = array_merge($classRegistry, require $extensionAutoloadFile);
552 }
553 } catch (\BadFunctionCallException $e) {
554 }
555 }
556 $this->extAutoloadClassFiles = $classRegistry;
557 }
558 return $this->extAutoloadClassFiles;
559 }
560
561 /**
562 * Returns a PackageInterface object for the specified package.
563 * A package is available, if the package directory contains valid MetaData information.
564 *
565 * @param string $packageKey
566 * @return \TYPO3\Flow\Package\PackageInterface The requested package object
567 * @throws \TYPO3\Flow\Package\Exception\UnknownPackageException if the specified package is not known
568 * @api
569 */
570 public function getPackage($packageKey) {
571 if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
572 $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
573 }
574 return parent::getPackage($packageKey);
575 }
576
577 /**
578 * Returns TRUE if a package is available (the package's files exist in the packages directory)
579 * or FALSE if it's not. If a package is available it doesn't mean necessarily that it's active!
580 *
581 * @param string $packageKey The key of the package to check
582 * @return boolean TRUE if the package is available, otherwise FALSE
583 * @api
584 */
585 public function isPackageAvailable($packageKey) {
586 if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
587 $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
588 }
589 return parent::isPackageAvailable($packageKey);
590 }
591
592 /**
593 * Returns TRUE if a package is activated or FALSE if it's not.
594 *
595 * @param string $packageKey The key of the package to check
596 * @return boolean TRUE if package is active, otherwise FALSE
597 * @api
598 */
599 public function isPackageActive($packageKey) {
600 if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
601 $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
602 }
603 return isset($this->runtimeActivatedPackages[$packageKey]) || parent::isPackageActive($packageKey);
604 }
605
606 /**
607 * @param string $packageKey
608 */
609 public function deactivatePackage($packageKey) {
610 $package = $this->getPackage($packageKey);
611 parent::deactivatePackage($package->getPackageKey());
612 }
613
614 /**
615 * @param string $packageKey
616 */
617 public function activatePackage($packageKey) {
618 $package = $this->getPackage($packageKey);
619 parent::activatePackage($package->getPackageKey());
620 $this->classLoader->addActivePackage($package);
621 }
622
623 /**
624 * Enables packages during runtime, but no class aliases will be available
625 *
626 * @param string $packageKey
627 * @api
628 */
629 public function activatePackageDuringRuntime($packageKey) {
630 $package = $this->getPackage($packageKey);
631 $this->runtimeActivatedPackages[$package->getPackageKey()] = $package;
632 $this->classLoader->addActivePackage($package);
633 if (!isset($GLOBALS['TYPO3_LOADED_EXT'][$package->getPackageKey()])) {
634 $loadedExtArrayElement = new \TYPO3\CMS\Core\Compatibility\LoadedExtensionArrayElement($package);
635 $GLOBALS['TYPO3_LOADED_EXT'][$package->getPackageKey()] = $loadedExtArrayElement->toArray();
636 }
637 }
638
639
640 /**
641 * @param string $packageKey
642 */
643 public function deletePackage($packageKey) {
644 $package = $this->getPackage($packageKey);
645 parent::deletePackage($package->getPackageKey());
646 }
647
648
649 /**
650 * @param string $packageKey
651 */
652 public function freezePackage($packageKey) {
653 $package = $this->getPackage($packageKey);
654 parent::freezePackage($package->getPackageKey());
655 }
656
657 /**
658 * @param string $packageKey
659 * @return bool
660 */
661 public function isPackageFrozen($packageKey) {
662 $package = $this->getPackage($packageKey);
663 return parent::isPackageFrozen($package->getPackageKey());
664 }
665
666 /**
667 * @param string $packageKey
668 */
669 public function unfreezePackage($packageKey) {
670 $package = $this->getPackage($packageKey);
671 parent::unfreezePackage($package->getPackageKey());
672 }
673
674 /**
675 * @param string $packageKey
676 */
677 public function refreezePackage($packageKey) {
678 $package = $this->getPackage($packageKey);
679 parent::refreezePackage($package->getPackageKey());
680 }
681
682 /**
683 * Returns an array of \TYPO3\Flow\Package objects of all active packages.
684 * A package is active, if it is available and has been activated in the package
685 * manager settings. This method returns runtime activated packages too
686 *
687 * @return \TYPO3\Flow\Package\PackageInterface[]
688 * @api
689 */
690 public function getActivePackages() {
691 return array_merge(parent::getActivePackages(), $this->runtimeActivatedPackages);
692 }
693
694 /**
695 * Orders all packages by comparing their dependencies. By this, the packages
696 * and package configurations arrays holds all packages in the correct
697 * initialization order.
698 *
699 * @return void
700 */
701 protected function sortAvailablePackagesByDependencies() {
702 $this->resolvePackageDependencies();
703
704 $this->packageStatesConfiguration['packages'] = $this->dependencyResolver->sortPackageStatesConfigurationByDependency($this->packageStatesConfiguration['packages']);
705
706 // Reorder the packages according to the loading order
707 $newPackages = array();
708 foreach (array_keys($this->packageStatesConfiguration['packages']) as $packageKey) {
709 $newPackages[$packageKey] = $this->packages[$packageKey];
710 // Remove the packagePathStack here again. We don't need it anymore and
711 // we don't want it in the package states file
712 unset($this->packageStatesConfiguration['packages'][$packageKey]['packagePathStack']);
713 }
714 $this->packages = $newPackages;
715 }
716
717 /**
718 * Resolves the dependent packages from the meta data of all packages recursively. The
719 * resolved direct or indirect dependencies of each package will put into the package
720 * states configuration array.
721 *
722 * @return void
723 */
724 protected function resolvePackageDependencies() {
725 parent::resolvePackageDependencies();
726 foreach ($this->packages as $packageKey => $package) {
727 $this->packageStatesConfiguration['packages'][$packageKey]['suggestions'] = $this->getSuggestionArrayForPackage($packageKey);
728 }
729 }
730
731 /**
732 * Returns an array of suggested package keys for the given package.
733 *
734 * @param string $packageKey The package key to fetch the suggestions for
735 * @return array|NULL An array of directly suggested packages
736 */
737 protected function getSuggestionArrayForPackage($packageKey) {
738 if (!isset($this->packages[$packageKey])) {
739 return NULL;
740 }
741 $suggestedPackageKeys = array();
742 $suggestedPackageConstraints = $this->packages[$packageKey]->getPackageMetaData()->getConstraintsByType(\TYPO3\Flow\Package\MetaDataInterface::CONSTRAINT_TYPE_SUGGESTS);
743 foreach ($suggestedPackageConstraints as $constraint) {
744 if ($constraint instanceof \TYPO3\Flow\Package\MetaData\PackageConstraint) {
745 $suggestedPackageKey = $constraint->getValue();
746 if (isset($this->packages[$suggestedPackageKey])) {
747 $suggestedPackageKeys[] = $suggestedPackageKey;
748 }
749 }
750 }
751 return array_reverse($suggestedPackageKeys);
752 }
753
754 /**
755 * Saves the current content of $this->packageStatesConfiguration to the
756 * PackageStates.php file.
757 *
758 * @return void
759 */
760 protected function sortAndSavePackageStates() {
761 parent::sortAndSavePackageStates();
762
763 $this->initializeCompatibilityLoadedExtArray();
764 \TYPO3\CMS\Core\Utility\OpcodeCacheUtility::clearAllActive($this->packageStatesPathAndFilename);
765 }
766
767 /**
768 * Check the conformance of the given package key
769 *
770 * @param string $packageKey The package key to validate
771 * @return boolean If the package key is valid, returns TRUE otherwise FALSE
772 * @api
773 */
774 public function isPackageKeyValid($packageKey) {
775 return parent::isPackageKeyValid($packageKey) || preg_match(\TYPO3\CMS\Core\Package\Package::PATTERN_MATCH_EXTENSIONKEY, $packageKey) === 1;
776 }
777 }