TYPO3 CMS  TYPO3_6-2
PackageManager.php
Go to the documentation of this file.
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 
24 
28  protected $classLoader;
29 
34 
38  protected $bootstrap;
39 
43  protected $coreCache;
44 
48  protected $cacheIdentifier;
49 
54 
58  protected $packagesBasePaths = array();
59 
63  protected $packageAliasMap = array();
64 
68  protected $requiredPackageKeys = array();
69 
73  protected $runtimeActivatedPackages = array();
74 
79  protected $packagesBasePath = PATH_site;
80 
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 
100  public function injectClassLoader(\TYPO3\CMS\Core\Core\ClassLoader $classLoader) {
101  $this->classLoader = $classLoader;
102  }
103 
107  public function injectCoreCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache) {
108  $this->coreCache = $coreCache;
109  }
110 
115  $this->dependencyResolver = $dependencyResolver;
116  }
117 
124  public function initialize(\TYPO3\Flow\Core\Bootstrap $bootstrap) {
125  $this->bootstrap = $bootstrap;
126 
127  $loadedFromCache = FALSE;
128  try {
130  $loadedFromCache = TRUE;
131  } catch (Exception\PackageManagerCacheUnavailableException $exception) {
132  $this->loadPackageStates();
133  $this->initializePackageObjects();
135  }
136 
137  //@deprecated since 6.2, don't use
138  if (!defined('REQUIRED_EXTENSIONS')) {
139  // List of extensions required to run the core
140  define('REQUIRED_EXTENSIONS', implode(',', $this->requiredPackageKeys));
141  }
142 
144  if ($cacheIdentifier === NULL) {
145  // Create an artificial cache identifier if the package states file is not available yet
146  // in order that the class loader and class alias map can cache anyways.
147  $cacheIdentifier = md5(implode('###', array_keys($this->activePackages)));
148  }
149  $this->classLoader->setCacheIdentifier($cacheIdentifier)->setPackages($this->activePackages);
150 
151  foreach ($this->activePackages as $package) {
153  $package->boot($bootstrap);
154  }
155 
156  if (!$loadedFromCache) {
157  $this->saveToPackageCache();
158  }
159  }
160 
164  protected function getPackageFactory() {
165  if (!isset($this->packageFactory)) {
166  $this->packageFactory = new PackageFactory($this);
167  }
168  return $this->packageFactory;
169  }
170 
174  protected function getCacheIdentifier() {
175  if ($this->cacheIdentifier === NULL) {
176  $mTime = @filemtime($this->packageStatesPathAndFilename);
177  if ($mTime !== FALSE) {
178  $this->cacheIdentifier = md5($this->packageStatesPathAndFilename . $mTime);
179  } else {
180  $this->cacheIdentifier = NULL;
181  }
182  }
183  return $this->cacheIdentifier;
184  }
185 
189  protected function getCacheEntryIdentifier() {
191  return $cacheIdentifier !== NULL ? 'PackageManager_' . $cacheIdentifier : NULL;
192  }
193 
197  protected function saveToPackageCache() {
198  $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
199  if ($cacheEntryIdentifier !== NULL && !$this->coreCache->has($cacheEntryIdentifier)) {
200  // Package objects get their own cache entry, so PHP does not have to parse the serialized string
201  $packageObjectsCacheEntryIdentifier = str_replace('.', '', uniqid('PackageObjects_', TRUE));
202  // Build cache file
203  $packageCache = array(
204  'packageStatesConfiguration' => $this->packageStatesConfiguration,
205  'packageAliasMap' => $this->packageAliasMap,
206  'packageKeys' => $this->packageKeys,
207  'activePackageKeys' => array_keys($this->activePackages),
208  'requiredPackageKeys' => $this->requiredPackageKeys,
209  'loadedExtArray' => $GLOBALS['TYPO3_LOADED_EXT'],
210  'packageObjectsCacheEntryIdentifier' => $packageObjectsCacheEntryIdentifier
211  );
212  $packageClassSources = array(
213  'typo3\\flow\\package\\package' => NULL,
214  'typo3\\cms\\core\\package\\package' => NULL,
215  );
216  foreach ($this->packages as $package) {
217  $packageClassName = strtolower(get_class($package));
218  if (!isset($packageClassSources[$packageClassName]) || $packageClassSources[$packageClassName] === NULL) {
219  $reflectionPackageClass = new \ReflectionClass($packageClassName);
220  $packageClassSource = file_get_contents($reflectionPackageClass->getFileName());
221  $packageClassSources[$packageClassName] = preg_replace('/<\?php|\?>/i', '', $packageClassSource);
222  }
223  }
224  $this->coreCache->set($packageObjectsCacheEntryIdentifier, serialize($this->packages));
225  $this->coreCache->set(
226  $cacheEntryIdentifier,
227  implode(PHP_EOL, $packageClassSources) . PHP_EOL .
228  'return ' . PHP_EOL . var_export($packageCache, TRUE) . ';'
229  );
230  }
231  }
232 
238  protected function loadPackageManagerStatesFromCache() {
239  $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
240  if ($cacheEntryIdentifier === NULL || !$this->coreCache->has($cacheEntryIdentifier) || !($packageCache = $this->coreCache->requireOnce($cacheEntryIdentifier))) {
241  throw new Exception\PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883342);
242  }
243  $this->packageStatesConfiguration = $packageCache['packageStatesConfiguration'];
244  $this->packageAliasMap = $packageCache['packageAliasMap'];
245  $this->packageKeys = $packageCache['packageKeys'];
246  $this->requiredPackageKeys = $packageCache['requiredPackageKeys'];
247  $GLOBALS['TYPO3_LOADED_EXT'] = $packageCache['loadedExtArray'];
248  $GLOBALS['TYPO3_currentPackageManager'] = $this;
249  // Strip off PHP Tags from Php Cache Frontend
250  $packageObjects = substr(substr($this->coreCache->get($packageCache['packageObjectsCacheEntryIdentifier']), 6), 0, -2);
251  $this->packages = unserialize($packageObjects);
252  foreach ($packageCache['activePackageKeys'] as $activePackageKey) {
253  $this->activePackages[$activePackageKey] = $this->packages[$activePackageKey];
254  }
255  unset($GLOBALS['TYPO3_currentPackageManager']);
256  }
257 
265  protected function loadPackageStates() {
266  $this->packageStatesConfiguration = @include($this->packageStatesPathAndFilename) ?: array();
267  if (!isset($this->packageStatesConfiguration['version']) || $this->packageStatesConfiguration['version'] < 4) {
268  $this->packageStatesConfiguration = array();
269  }
270  if ($this->packageStatesConfiguration !== array()) {
272  } else {
273  throw new Exception\PackageStatesUnavailableException('The PackageStates.php file is either corrupt or unavailable.', 1381507733);
274  }
275  }
276 
284  protected function initializePackageObjects() {
285  $requiredPackages = array();
286  foreach ($this->packages as $packageKey => $package) {
287  $protected = $package->isProtected();
288  if ($protected) {
289  $requiredPackages[$packageKey] = $package;
290  }
291  if (isset($this->packageStatesConfiguration['packages'][$packageKey]['state']) && $this->packageStatesConfiguration['packages'][$packageKey]['state'] === 'active') {
292  $this->activePackages[$packageKey] = $package;
293  }
294  }
295  $previousActivePackage = $this->activePackages;
296  $this->activePackages = array_merge($requiredPackages, $this->activePackages);
297  $this->requiredPackageKeys = array_keys($requiredPackages);
298 
299  if ($this->activePackages != $previousActivePackage) {
300  foreach ($this->requiredPackageKeys as $requiredPackageKey) {
301  $this->packageStatesConfiguration['packages'][$requiredPackageKey]['state'] = 'active';
302  }
303  $this->sortAndSavePackageStates();
304  }
305  }
306 
313  $loadedExtObj = new \TYPO3\CMS\Core\Compatibility\LoadedExtensionsArray($this);
314  $GLOBALS['TYPO3_LOADED_EXT'] = $loadedExtObj->toArray();
315  }
316 
317 
325  public function scanAvailablePackages() {
326  $previousPackageStatesConfiguration = $this->packageStatesConfiguration;
327 
328  if (isset($this->packageStatesConfiguration['packages'])) {
329  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $configuration) {
330  if (!@file_exists($this->packagesBasePath . $configuration['packagePath'])) {
331  unset($this->packageStatesConfiguration['packages'][$packageKey]);
332  }
333  }
334  } else {
335  $this->packageStatesConfiguration['packages'] = array();
336  }
337 
338  foreach ($this->packagesBasePaths as $key => $packagesBasePath) {
339  if (!is_dir($packagesBasePath)) {
340  unset($this->packagesBasePaths[$key]);
341  }
342  }
343 
344  $packagePaths = $this->scanLegacyExtensions();
345  foreach ($this->packagesBasePaths as $packagesBasePath) {
346  $this->scanPackagesInPath($packagesBasePath, $packagePaths);
347  }
348 
349  foreach ($packagePaths as $composerManifestPath) {
350  $packagePath = $composerManifestPath;
351  $packagesBasePath = PATH_site;
352  foreach ($this->packagesBasePaths as $basePath) {
353  if (strpos($packagePath, $basePath) === 0) {
354  $packagesBasePath = $basePath;
355  break;
356  }
357  }
358  try {
359  $composerManifest = self::getComposerManifest($composerManifestPath);
360  $packageKey = PackageFactory::getPackageKeyFromManifest($composerManifest, $packagePath, $packagesBasePath);
361  $this->composerNameToPackageKeyMap[strtolower($composerManifest->name)] = $packageKey;
362  $this->packageStatesConfiguration['packages'][$packageKey]['manifestPath'] = substr($composerManifestPath, strlen($packagePath)) ? : '';
363  $this->packageStatesConfiguration['packages'][$packageKey]['composerName'] = $composerManifest->name;
364  } catch (\TYPO3\Flow\Package\Exception\MissingPackageManifestException $exception) {
365  $relativePackagePath = substr($packagePath, strlen($packagesBasePath));
366  $packageKey = substr($relativePackagePath, strpos($relativePackagePath, '/') + 1, -1);
367  if (!$this->isPackageKeyValid($packageKey)) {
368  continue;
369  }
370  } catch (\TYPO3\Flow\Package\Exception\InvalidPackageKeyException $exception) {
371  continue;
372  }
373  if (!isset($this->packageStatesConfiguration['packages'][$packageKey]['state'])) {
374  $this->packageStatesConfiguration['packages'][$packageKey]['state'] = 'inactive';
375  }
376 
377  $this->packageStatesConfiguration['packages'][$packageKey]['packagePath'] = str_replace($this->packagesBasePath, '', $packagePath);
378 
379  // Change this to read the target from Composer or any other source
380  $this->packageStatesConfiguration['packages'][$packageKey]['classesPath'] = Package::DIRECTORY_CLASSES;
381  }
382 
383  $registerOnlyNewPackages = !empty($this->packages);
384  $this->registerPackagesFromConfiguration($registerOnlyNewPackages);
385  if ($this->packageStatesConfiguration != $previousPackageStatesConfiguration) {
386  $this->sortAndsavePackageStates();
387  }
388  }
389 
394  protected function scanLegacyExtensions(&$collectedExtensionPaths = array()) {
395  $legacyCmsPackageBasePathTypes = array('sysext', 'global', 'local');
396  foreach ($this->packagesBasePaths as $type => $packageBasePath) {
397  if (!in_array($type, $legacyCmsPackageBasePathTypes)) {
398  continue;
399  }
401  foreach (new \DirectoryIterator($packageBasePath) as $fileInfo) {
402  if (!$fileInfo->isDir()) {
403  continue;
404  }
405  $filename = $fileInfo->getFilename();
406  if ($filename[0] !== '.') {
407  $currentPath = \TYPO3\Flow\Utility\Files::getUnixStylePath($fileInfo->getPathName()) . '/';
408  // Only add the extension if we have an EMCONF and the extension is not yet registered.
409  // This is crucial in order to allow overriding of system extension by local extensions
410  // and strongly depends on the order of paths defined in $this->packagesBasePaths.
411  if (file_exists($currentPath . 'ext_emconf.php') && !isset($collectedExtensionPaths[$filename])) {
412  $collectedExtensionPaths[$filename] = $currentPath;
413  }
414  }
415  }
416  }
417  return $collectedExtensionPaths;
418  }
419 
426  protected function findComposerManifestPaths($packagePath) {
427  // If an ext_emconf.php file is found, we don't need to look deeper
428  if (file_exists($packagePath . '/ext_emconf.php')) {
429  return array();
430  }
431  return parent::findComposerManifestPaths($packagePath);
432  }
433 
441  protected function registerPackagesFromConfiguration($registerOnlyNewPackages = FALSE) {
442  $packageStatesHasChanged = FALSE;
443  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $stateConfiguration) {
444 
445  if ($registerOnlyNewPackages && $this->isPackageAvailable($packageKey)) {
446  continue;
447  }
448 
449  $packagePath = isset($stateConfiguration['packagePath']) ? $stateConfiguration['packagePath'] : NULL;
450  $classesPath = isset($stateConfiguration['classesPath']) ? $stateConfiguration['classesPath'] : NULL;
451  $manifestPath = isset($stateConfiguration['manifestPath']) ? $stateConfiguration['manifestPath'] : NULL;
452 
453  try {
454  $package = $this->getPackageFactory()->create($this->packagesBasePath, $packagePath, $packageKey, $classesPath, $manifestPath);
455  } catch (\TYPO3\Flow\Package\Exception\InvalidPackagePathException $exception) {
456  $this->unregisterPackageByPackageKey($packageKey);
457  $packageStatesHasChanged = TRUE;
458  continue;
459  } catch (\TYPO3\Flow\Package\Exception\InvalidPackageKeyException $exception) {
460  $this->unregisterPackageByPackageKey($packageKey);
461  $packageStatesHasChanged = TRUE;
462  continue;
463  }
464 
465  $this->registerPackage($package, FALSE);
466 
467  if (!$this->packages[$packageKey] instanceof \TYPO3\Flow\Package\PackageInterface) {
468  throw new \TYPO3\Flow\Package\Exception\CorruptPackageException(sprintf('The package class in package "%s" does not implement PackageInterface.', $packageKey), 1300782488);
469  }
470 
471  $this->packageKeys[strtolower($packageKey)] = $packageKey;
472  if ($stateConfiguration['state'] === 'active') {
473  $this->activePackages[$packageKey] = $this->packages[$packageKey];
474  }
475  }
476  if ($packageStatesHasChanged) {
477  $this->sortAndSavePackageStates();
478  }
479  }
480 
489  public function registerPackage(\TYPO3\Flow\Package\PackageInterface $package, $sortAndSave = TRUE) {
490  $package = parent::registerPackage($package, $sortAndSave);
491  if ($package instanceof PackageInterface) {
492  foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
493  $this->packageAliasMap[strtolower($packageToReplace)] = $package->getPackageKey();
494  }
495  }
496  return $package;
497  }
498 
505  protected function unregisterPackageByPackageKey($packageKey) {
506  try {
507  $package = $this->getPackage($packageKey);
508  if ($package instanceof PackageInterface) {
509  foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
510  unset($this->packageAliasMap[strtolower($packageToReplace)]);
511  }
512  $packageKey = $package->getPackageKey();
513  }
514  } catch (\TYPO3\Flow\Package\Exception\UnknownPackageException $e) {
515  }
516  unset($this->packages[$packageKey]);
517  unset($this->packageKeys[strtolower($packageKey)]);
518  unset($this->packageStatesConfiguration['packages'][$packageKey]);
519  }
520 
528  public function getPackageKeyFromComposerName($composerName) {
529  if (isset($this->packageAliasMap[$composerName])) {
530  return $this->packageAliasMap[$composerName];
531  }
532  try {
533  return parent::getPackageKeyFromComposerName($composerName);
534  } catch (\TYPO3\Flow\Package\Exception\InvalidPackageStateException $exception) {
535  return $composerName;
536  }
537  }
538 
542  public function getExtAutoloadRegistry() {
543  if (!isset($this->extAutoloadClassFiles)) {
544  $classRegistry = array();
545  foreach ($this->activePackages as $packageKey => $packageData) {
546  try {
547  $extensionAutoloadFile = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($packageKey, 'ext_autoload.php');
548  if (@file_exists($extensionAutoloadFile)) {
549  $classRegistry = array_merge($classRegistry, require $extensionAutoloadFile);
550  }
551  } catch (\BadFunctionCallException $e) {
552  }
553  }
554  $this->extAutoloadClassFiles = $classRegistry;
555  }
557  }
558 
568  public function getPackage($packageKey) {
569  if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
570  $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
571  }
572  return parent::getPackage($packageKey);
573  }
574 
583  public function isPackageAvailable($packageKey) {
584  if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
585  $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
586  }
587  return parent::isPackageAvailable($packageKey);
588  }
589 
597  public function isPackageActive($packageKey) {
598  if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
599  $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
600  }
601  return isset($this->runtimeActivatedPackages[$packageKey]) || parent::isPackageActive($packageKey);
602  }
603 
607  public function deactivatePackage($packageKey) {
608  $package = $this->getPackage($packageKey);
609  parent::deactivatePackage($package->getPackageKey());
610  }
611 
615  public function activatePackage($packageKey) {
616  $package = $this->getPackage($packageKey);
617  parent::activatePackage($package->getPackageKey());
618  $this->classLoader->addActivePackage($package);
619  }
620 
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 
641  public function deletePackage($packageKey) {
642  $package = $this->getPackage($packageKey);
643  parent::deletePackage($package->getPackageKey());
644  }
645 
646 
650  public function freezePackage($packageKey) {
651  $package = $this->getPackage($packageKey);
652  parent::freezePackage($package->getPackageKey());
653  }
654 
659  public function isPackageFrozen($packageKey) {
660  $package = $this->getPackage($packageKey);
661  return parent::isPackageFrozen($package->getPackageKey());
662  }
663 
667  public function unfreezePackage($packageKey) {
668  $package = $this->getPackage($packageKey);
669  parent::unfreezePackage($package->getPackageKey());
670  }
671 
675  public function refreezePackage($packageKey) {
676  $package = $this->getPackage($packageKey);
677  parent::refreezePackage($package->getPackageKey());
678  }
679 
688  public function getActivePackages() {
689  return array_merge(parent::getActivePackages(), $this->runtimeActivatedPackages);
690  }
691 
699  protected function sortAvailablePackagesByDependencies() {
701 
702  // sort the packages by key at first, so we get a stable sorting of "equivalent" packages afterwards
703  ksort($this->packageStatesConfiguration['packages']);
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 ($this->packageStatesConfiguration['packages'] as $packageKey => $_) {
709  $newPackages[$packageKey] = $this->packages[$packageKey];
710  }
711  $this->packages = $newPackages;
712  }
713 
721  protected function resolvePackageDependencies() {
722  parent::resolvePackageDependencies();
723  foreach ($this->packages as $packageKey => $package) {
724  $this->packageStatesConfiguration['packages'][$packageKey]['suggestions'] = $this->getSuggestionArrayForPackage($packageKey);
725  }
726  }
727 
734  protected function getSuggestionArrayForPackage($packageKey) {
735  if (!isset($this->packages[$packageKey])) {
736  return NULL;
737  }
738  $suggestedPackageKeys = array();
739  $suggestedPackageConstraints = $this->packages[$packageKey]->getPackageMetaData()->getConstraintsByType(\TYPO3\Flow\Package\MetaDataInterface::CONSTRAINT_TYPE_SUGGESTS);
740  foreach ($suggestedPackageConstraints as $constraint) {
741  if ($constraint instanceof \TYPO3\Flow\Package\MetaData\PackageConstraint) {
742  $suggestedPackageKey = $constraint->getValue();
743  if (isset($this->packages[$suggestedPackageKey])) {
744  $suggestedPackageKeys[] = $suggestedPackageKey;
745  }
746  }
747  }
748  return array_reverse($suggestedPackageKeys);
749  }
750 
757  protected function sortAndSavePackageStates() {
758  parent::sortAndSavePackageStates();
759  \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($this->packageStatesPathAndFilename);
760 
762  \TYPO3\CMS\Core\Utility\OpcodeCacheUtility::clearAllActive($this->packageStatesPathAndFilename);
763  }
764 
772  public function isPackageKeyValid($packageKey) {
773  return parent::isPackageKeyValid($packageKey) || preg_match(\TYPO3\CMS\Core\Package\Package::PATTERN_MATCH_EXTENSIONKEY, $packageKey) === 1;
774  }
775 }
injectCoreCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache)
static getPackageKeyFromManifest($manifest, $packagePath, $packagesBasePath)
scanPackagesInPath($startPath, array &$collectedPackagePaths=array())
initialize(\TYPO3\Flow\Core\Bootstrap $bootstrap)
static fixPermissions($path, $recursive=FALSE)
injectClassLoader(\TYPO3\CMS\Core\Core\ClassLoader $classLoader)
injectDependencyResolver(DependencyResolver $dependencyResolver)
registerPackagesFromConfiguration($registerOnlyNewPackages=FALSE)
registerPackage(\TYPO3\Flow\Package\PackageInterface $package, $sortAndSave=TRUE)
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
static getUnixStylePath($path)
Definition: Files.php:27