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