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