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