TYPO3 CMS  TYPO3_7-6
PackageManager.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Package;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
25 
31 {
36 
40  protected $bootstrap;
41 
45  protected $coreCache;
46 
50  protected $cacheIdentifier;
51 
55  protected $packagesBasePaths = [];
56 
60  protected $packageAliasMap = [];
61 
65  protected $requiredPackageKeys = [];
66 
70  protected $runtimeActivatedPackages = [];
71 
76  protected $packagesBasePath = PATH_site;
77 
82  protected $packages = [];
83 
88  protected $packageKeys = [];
89 
95 
100  protected $activePackages = [];
101 
106 
112 
116  public function __construct()
117  {
118  // The order of paths is crucial for allowing overriding of system extension by local extensions.
119  // Pay attention if you change order of the paths here.
120  $this->packagesBasePaths = [
121  'local' => PATH_typo3conf . 'ext',
122  'global' => PATH_typo3 . 'ext',
123  'sysext' => PATH_typo3 . 'sysext',
124  ];
125  $this->packageStatesPathAndFilename = PATH_typo3conf . 'PackageStates.php';
126  }
127 
131  public function injectCoreCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache)
132  {
133  $this->coreCache = $coreCache;
134  }
135 
140  {
141  $this->dependencyResolver = $dependencyResolver;
142  }
143 
151  public function initialize(Bootstrap $bootstrap)
152  {
153  $this->bootstrap = $bootstrap;
154 
155  $loadedFromCache = false;
156  try {
158  $loadedFromCache = true;
159  } catch (Exception\PackageManagerCacheUnavailableException $exception) {
160  $this->loadPackageStates();
161  $this->initializePackageObjects();
163  }
164 
165  if (!$loadedFromCache) {
166  $this->saveToPackageCache();
167  }
168  }
169 
173  protected function getCacheIdentifier()
174  {
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()
190  {
192  return $cacheIdentifier !== null ? 'PackageManager_' . $cacheIdentifier : null;
193  }
194 
198  protected function saveToPackageCache()
199  {
200  $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
201  if ($cacheEntryIdentifier !== null && !$this->coreCache->has($cacheEntryIdentifier)) {
202  // Package objects get their own cache entry, so PHP does not have to parse the serialized string
203  $packageObjectsCacheEntryIdentifier = StringUtility::getUniqueId('PackageObjects_');
204  // Build cache file
205  $packageCache = [
206  'packageStatesConfiguration' => $this->packageStatesConfiguration,
207  'packageAliasMap' => $this->packageAliasMap,
208  'packageKeys' => $this->packageKeys,
209  'activePackageKeys' => array_keys($this->activePackages),
210  'requiredPackageKeys' => $this->requiredPackageKeys,
211  'loadedExtArray' => $GLOBALS['TYPO3_LOADED_EXT'],
212  'packageObjectsCacheEntryIdentifier' => $packageObjectsCacheEntryIdentifier
213  ];
214  $this->coreCache->set($packageObjectsCacheEntryIdentifier, serialize($this->packages));
215  $this->coreCache->set(
216  $cacheEntryIdentifier,
217  'return ' . PHP_EOL . var_export($packageCache, true) . ';'
218  );
219  }
220  }
221 
228  {
229  $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
230  if ($cacheEntryIdentifier === null || !$this->coreCache->has($cacheEntryIdentifier) || !($packageCache = $this->coreCache->requireOnce($cacheEntryIdentifier))) {
231  throw new Exception\PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883342);
232  }
233  $this->packageStatesConfiguration = $packageCache['packageStatesConfiguration'];
234  $this->packageAliasMap = $packageCache['packageAliasMap'];
235  $this->packageKeys = $packageCache['packageKeys'];
236  $this->requiredPackageKeys = $packageCache['requiredPackageKeys'];
237  $GLOBALS['TYPO3_LOADED_EXT'] = $packageCache['loadedExtArray'];
238  $GLOBALS['TYPO3_currentPackageManager'] = $this;
239  // Strip off PHP Tags from Php Cache Frontend
240  $packageObjects = substr(substr($this->coreCache->get($packageCache['packageObjectsCacheEntryIdentifier']), 6), 0, -2);
241  $this->packages = unserialize($packageObjects);
242  foreach ($packageCache['activePackageKeys'] as $activePackageKey) {
243  $this->activePackages[$activePackageKey] = $this->packages[$activePackageKey];
244  }
245  unset($GLOBALS['TYPO3_currentPackageManager']);
246  }
247 
255  protected function loadPackageStates()
256  {
257  $this->packageStatesConfiguration = @include($this->packageStatesPathAndFilename) ?: [];
258  if (!isset($this->packageStatesConfiguration['version']) || $this->packageStatesConfiguration['version'] < 4) {
259  $this->packageStatesConfiguration = [];
260  }
261  if ($this->packageStatesConfiguration !== []) {
263  } else {
264  throw new Exception\PackageStatesUnavailableException('The PackageStates.php file is either corrupt or unavailable.', 1381507733);
265  }
266  }
267 
275  protected function initializePackageObjects()
276  {
277  $requiredPackages = [];
278  foreach ($this->packages as $packageKey => $package) {
279  if ($package->isProtected()) {
280  $requiredPackages[$packageKey] = $package;
281  }
282  if (isset($this->packageStatesConfiguration['packages'][$packageKey]['state']) && $this->packageStatesConfiguration['packages'][$packageKey]['state'] === 'active') {
283  $this->activePackages[$packageKey] = $package;
284  }
285  }
286  $previousActivePackages = $this->activePackages;
287  $this->activePackages = array_merge($requiredPackages, $this->activePackages);
288  $this->requiredPackageKeys = array_keys($requiredPackages);
289 
290  if ($this->activePackages != $previousActivePackages) {
291  foreach ($this->requiredPackageKeys as $requiredPackageKey) {
292  $this->packageStatesConfiguration['packages'][$requiredPackageKey]['state'] = 'active';
293  }
294  $this->sortAndSavePackageStates();
295  }
296  }
297 
304  {
305  $loadedExtObj = new \TYPO3\CMS\Core\Compatibility\LoadedExtensionsArray($this);
306  $GLOBALS['TYPO3_LOADED_EXT'] = $loadedExtObj->toArray();
307  }
308 
315  public function scanAvailablePackages()
316  {
317  $previousPackageStatesConfiguration = $this->packageStatesConfiguration;
318 
319  if (isset($this->packageStatesConfiguration['packages'])) {
320  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $configuration) {
321  if (!@file_exists($this->packagesBasePath . $configuration['packagePath'])) {
322  unset($this->packageStatesConfiguration['packages'][$packageKey]);
323  }
324  }
325  } else {
326  $this->packageStatesConfiguration['packages'] = [];
327  }
328 
329  foreach ($this->packagesBasePaths as $key => $packagesBasePath) {
330  if (!is_dir($packagesBasePath)) {
331  unset($this->packagesBasePaths[$key]);
332  }
333  }
334 
335  $packagePaths = $this->scanLegacyExtensions();
336  foreach ($this->packagesBasePaths as $packagesBasePath) {
337  $packagePaths = $this->scanPackagesInPath($packagesBasePath, $packagePaths);
338  }
339 
340  foreach ($packagePaths as $packagePath) {
341  $packagesBasePath = $this->packagesBasePath;
342  foreach ($this->packagesBasePaths as $basePath) {
343  if (strpos($packagePath, $basePath) === 0) {
344  $packagesBasePath = $basePath;
345  break;
346  }
347  }
348  try {
349  $composerManifest = $this->getComposerManifest($packagePath);
350  $packageKey = $this->getPackageKeyFromManifest($composerManifest, $packagePath, $packagesBasePath);
351  $this->composerNameToPackageKeyMap[strtolower($composerManifest->name)] = $packageKey;
352  $this->packageStatesConfiguration['packages'][$packageKey]['composerName'] = $composerManifest->name;
353  } catch (Exception\MissingPackageManifestException $exception) {
354  $relativePackagePath = substr($packagePath, strlen($packagesBasePath));
355  $packageKey = substr($relativePackagePath, strpos($relativePackagePath, '/') + 1, -1);
356  if (!$this->isPackageKeyValid($packageKey)) {
357  continue;
358  }
359  } catch (Exception\InvalidPackageKeyException $exception) {
360  continue;
361  }
362  if (!isset($this->packageStatesConfiguration['packages'][$packageKey]['state'])) {
363  $this->packageStatesConfiguration['packages'][$packageKey]['state'] = 'inactive';
364  }
365 
366  $this->packageStatesConfiguration['packages'][$packageKey]['packagePath'] = str_replace($this->packagesBasePath, '', $packagePath);
367  }
368 
369  $registerOnlyNewPackages = !empty($this->packages);
370  $this->registerPackagesFromConfiguration($registerOnlyNewPackages);
371  if ($this->packageStatesConfiguration != $previousPackageStatesConfiguration) {
372  $this->sortAndsavePackageStates();
373  }
374  }
375 
382  protected function scanLegacyExtensions(&$collectedExtensionPaths = [])
383  {
384  $legacyCmsPackageBasePathTypes = ['sysext', 'global', 'local'];
385  foreach ($this->packagesBasePaths as $type => $packageBasePath) {
386  if (!in_array($type, $legacyCmsPackageBasePathTypes, true)) {
387  continue;
388  }
390  foreach (new \DirectoryIterator($packageBasePath) as $fileInfo) {
391  if (!$fileInfo->isDir()) {
392  continue;
393  }
394  $filename = $fileInfo->getFilename();
395  if ($filename[0] !== '.') {
396  // Fix Windows backslashes
397  // we can't use GeneralUtility::fixWindowsFilePath as we have to keep double slashes for Unit Tests (vfs://)
398  $currentPath = str_replace('\\', '/', $fileInfo->getPathName()) . '/';
399  // Only add the extension if we have an EMCONF and the extension is not yet registered.
400  // This is crucial in order to allow overriding of system extension by local extensions
401  // and strongly depends on the order of paths defined in $this->packagesBasePaths.
402  if (file_exists($currentPath . 'ext_emconf.php') && !isset($collectedExtensionPaths[$filename])) {
403  $collectedExtensionPaths[$filename] = $currentPath;
404  }
405  }
406  }
407  }
408  return $collectedExtensionPaths;
409  }
410 
418  protected function hasComposerManifestFile($packagePath)
419  {
420  // If an ext_emconf.php file is found, we don't need to look further
421  if (file_exists($packagePath . 'ext_emconf.php')) {
422  return false;
423  }
424  if (file_exists($packagePath . 'composer.json')) {
425  return true;
426  }
427  return false;
428  }
429 
436  protected function registerPackagesFromConfiguration($registerOnlyNewPackages = false)
437  {
438  $packageStatesHasChanged = false;
439  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $stateConfiguration) {
440  if ($registerOnlyNewPackages && $this->isPackageAvailable($packageKey)) {
441  continue;
442  }
443 
444  if (!isset($stateConfiguration['packagePath'])) {
445  $this->unregisterPackageByPackageKey($packageKey);
446  $packageStatesHasChanged = true;
447  continue;
448  }
449 
450  try {
451  $packagePath = PathUtility::sanitizeTrailingSeparator($this->packagesBasePath . $stateConfiguration['packagePath']);
452  $package = new Package($this, $packageKey, $packagePath);
453  } catch (Exception\InvalidPackagePathException $exception) {
454  $this->unregisterPackageByPackageKey($packageKey);
455  $packageStatesHasChanged = true;
456  continue;
457  } catch (Exception\InvalidPackageKeyException $exception) {
458  $this->unregisterPackageByPackageKey($packageKey);
459  $packageStatesHasChanged = true;
460  continue;
461  } catch (Exception\InvalidPackageManifestException $exception) {
462  $this->unregisterPackageByPackageKey($packageKey);
463  $packageStatesHasChanged = true;
464  continue;
465  }
466 
467  $this->registerPackage($package, false);
468 
469  $this->packageKeys[strtolower($packageKey)] = $packageKey;
470  if ($stateConfiguration['state'] === 'active') {
471  $this->activePackages[$packageKey] = $this->packages[$packageKey];
472  }
473  }
474  if ($packageStatesHasChanged) {
475  $this->sortAndSavePackageStates();
476  }
477  }
478 
488  public function registerPackage(PackageInterface $package, $sortAndSave = true)
489  {
490  $packageKey = $package->getPackageKey();
491  if ($this->isPackageAvailable($packageKey)) {
492  throw new Exception\InvalidPackageStateException('Package "' . $packageKey . '" is already registered.', 1338996122);
493  }
494 
495  $this->packages[$packageKey] = $package;
496  $this->packageStatesConfiguration['packages'][$packageKey]['packagePath'] = str_replace($this->packagesBasePath, '', $package->getPackagePath());
497 
498  if ($sortAndSave === true) {
499  $this->sortAndSavePackageStates();
500  }
501 
502  if ($package instanceof PackageInterface) {
503  foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
504  $this->packageAliasMap[strtolower($packageToReplace)] = $package->getPackageKey();
505  }
506  }
507  return $package;
508  }
509 
516  protected function unregisterPackageByPackageKey($packageKey)
517  {
518  try {
519  $package = $this->getPackage($packageKey);
520  if ($package instanceof PackageInterface) {
521  foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
522  unset($this->packageAliasMap[strtolower($packageToReplace)]);
523  }
524  }
525  } catch (Exception\UnknownPackageException $e) {
526  }
527  unset($this->packages[$packageKey]);
528  unset($this->packageKeys[strtolower($packageKey)]);
529  unset($this->packageStatesConfiguration['packages'][$packageKey]);
530  }
531 
538  public function getPackageKeyFromComposerName($composerName)
539  {
540  if (isset($this->packageAliasMap[$composerName])) {
541  return $this->packageAliasMap[$composerName];
542  }
543  if (empty($this->composerNameToPackageKeyMap)) {
544  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $packageStateConfiguration) {
545  $this->composerNameToPackageKeyMap[strtolower($packageStateConfiguration['composerName'])] = $packageKey;
546  }
547  // Hard coded compatibility layer for old cms extension
548  // @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8
549  $this->composerNameToPackageKeyMap['typo3/cms-cms'] = 'cms';
550  }
551  $lowercasedComposerName = strtolower($composerName);
552  if (!isset($this->composerNameToPackageKeyMap[$lowercasedComposerName])) {
553  return $composerName;
554  }
555  return $this->composerNameToPackageKeyMap[$lowercasedComposerName];
556  }
557 
567  public function getPackage($packageKey)
568  {
569  if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
570  $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
571  }
572  if (!$this->isPackageAvailable($packageKey)) {
573  throw new Exception\UnknownPackageException('Package "' . $packageKey . '" is not available. Please check if the package exists and that the package key is correct (package keys are case sensitive).', 1166546734);
574  }
575  return $this->packages[$packageKey];
576  }
577 
586  public function isPackageAvailable($packageKey)
587  {
588  if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
589  $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
590  }
591  return isset($this->packages[$packageKey]);
592  }
593 
601  public function isPackageActive($packageKey)
602  {
603  return isset($this->runtimeActivatedPackages[$packageKey]) || isset($this->activePackages[$packageKey]);
604  }
605 
614  public function deactivatePackage($packageKey)
615  {
617 
618  foreach ($this->packageStatesConfiguration['packages'] as $packageStateKey => $packageStateConfiguration) {
619  if ($packageKey === $packageStateKey || empty($packageStateConfiguration['dependencies']) || $packageStateConfiguration['state'] !== 'active') {
620  continue;
621  }
622  if (in_array($packageKey, $packageStateConfiguration['dependencies'], true)) {
623  $this->deactivatePackage($packageStateKey);
624  }
625  }
626 
627  if (!$this->isPackageActive($packageKey)) {
628  return;
629  }
630 
631  $package = $this->getPackage($packageKey);
632  if ($package->isProtected()) {
633  throw new Exception\ProtectedPackageKeyException('The package "' . $packageKey . '" is protected and cannot be deactivated.', 1308662891);
634  }
635 
636  unset($this->activePackages[$packageKey]);
637  $this->packageStatesConfiguration['packages'][$packageKey]['state'] = 'inactive';
638  $this->sortAndSavePackageStates();
639  }
640 
644  public function activatePackage($packageKey)
645  {
646  $package = $this->getPackage($packageKey);
648 
649  if ($this->isPackageActive($packageKey)) {
650  return;
651  }
652 
653  $this->activePackages[$packageKey] = $package;
654  $this->packageStatesConfiguration['packages'][$packageKey]['state'] = 'active';
655  if (!isset($this->packageStatesConfiguration['packages'][$packageKey]['packagePath'])) {
656  $this->packageStatesConfiguration['packages'][$packageKey]['packagePath'] = str_replace($this->packagesBasePath, '', $package->getPackagePath());
657  }
658  $this->sortAndSavePackageStates();
659  }
660 
667  public function activatePackageDuringRuntime($packageKey)
668  {
669  $package = $this->getPackage($packageKey);
670  $this->runtimeActivatedPackages[$package->getPackageKey()] = $package;
671  if (!isset($GLOBALS['TYPO3_LOADED_EXT'][$package->getPackageKey()])) {
672  $loadedExtArrayElement = new LoadedExtensionArrayElement($package);
673  $GLOBALS['TYPO3_LOADED_EXT'][$package->getPackageKey()] = $loadedExtArrayElement->toArray();
674  }
676  }
677 
683  {
685  return;
686  }
687  ClassLoadingInformation::registerTransientClassLoadingInformationForPackage($package);
688  }
689 
699  public function deletePackage($packageKey)
700  {
701  if (!$this->isPackageAvailable($packageKey)) {
702  throw new Exception\UnknownPackageException('Package "' . $packageKey . '" is not available and cannot be removed.', 1166543253);
703  }
704 
705  $package = $this->getPackage($packageKey);
706  if ($package->isProtected()) {
707  throw new Exception\ProtectedPackageKeyException('The package "' . $packageKey . '" is protected and cannot be removed.', 1220722120);
708  }
709 
710  if ($this->isPackageActive($packageKey)) {
711  $this->deactivatePackage($packageKey);
712  }
713 
714  $this->unregisterPackage($package);
715  $this->sortAndSavePackageStates();
716 
717  $packagePath = $package->getPackagePath();
718  $deletion = GeneralUtility::rmdir($packagePath, true);
719  if ($deletion === false) {
720  throw new Exception('Please check file permissions. The directory "' . $packagePath . '" for package "' . $packageKey . '" could not be removed.', 1301491089);
721  }
722  }
723 
732  public function getActivePackages()
733  {
734  return array_merge($this->activePackages, $this->runtimeActivatedPackages);
735  }
736 
745  {
747 
748  // sort the packages by key at first, so we get a stable sorting of "equivalent" packages afterwards
749  ksort($this->packageStatesConfiguration['packages']);
750  $this->packageStatesConfiguration['packages'] = $this->dependencyResolver->sortPackageStatesConfigurationByDependency($this->packageStatesConfiguration['packages']);
751 
752  // Reorder the packages according to the loading order
753  $newPackages = [];
754  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $_) {
755  $newPackages[$packageKey] = $this->packages[$packageKey];
756  }
757  $this->packages = $newPackages;
758  }
759 
767  protected function resolvePackageDependencies()
768  {
769  foreach ($this->packages as $packageKey => $package) {
770  $this->packageStatesConfiguration['packages'][$packageKey]['dependencies'] = $this->getDependencyArrayForPackage($packageKey);
771  }
772  foreach ($this->packages as $packageKey => $package) {
773  $this->packageStatesConfiguration['packages'][$packageKey]['suggestions'] = $this->getSuggestionArrayForPackage($packageKey);
774  }
775  }
776 
783  protected function getSuggestionArrayForPackage($packageKey)
784  {
785  if (!isset($this->packages[$packageKey])) {
786  return null;
787  }
788  $suggestedPackageKeys = [];
789  $suggestedPackageConstraints = $this->packages[$packageKey]->getPackageMetaData()->getConstraintsByType(MetaData::CONSTRAINT_TYPE_SUGGESTS);
790  foreach ($suggestedPackageConstraints as $constraint) {
791  if ($constraint instanceof MetaData\PackageConstraint) {
792  $suggestedPackageKey = $constraint->getValue();
793  if (isset($this->packages[$suggestedPackageKey])) {
794  $suggestedPackageKeys[] = $suggestedPackageKey;
795  }
796  }
797  }
798  return array_reverse($suggestedPackageKeys);
799  }
800 
807  protected function sortAndSavePackageStates()
808  {
810 
811  $this->packageStatesConfiguration['version'] = 4;
812 
813  $fileDescription = "# PackageStates.php\n\n";
814  $fileDescription .= "# This file is maintained by TYPO3's package management. Although you can edit it\n";
815  $fileDescription .= "# manually, you should rather use the extension manager for maintaining packages.\n";
816  $fileDescription .= "# This file will be regenerated automatically if it doesn't exist. Deleting this file\n";
817  $fileDescription .= "# should, however, never become necessary if you use the package commands.\n";
818 
819  // We do not need the dependencies on disk...
820  foreach ($this->packageStatesConfiguration['packages'] as &$packageConfiguration) {
821  if (isset($packageConfiguration['dependencies'])) {
822  unset($packageConfiguration['dependencies']);
823  }
824  }
825  if (!@is_writable($this->packageStatesPathAndFilename)) {
826  // If file does not exists try to create it
827  $fileHandle = @fopen($this->packageStatesPathAndFilename, 'x');
828  if (!$fileHandle) {
829  throw new Exception\PackageStatesFileNotWritableException(
830  sprintf('We could not update the list of installed packages because the file %s is not writable. Please, check the file system permissions for this file and make sure that the web server can update it.', $this->packageStatesPathAndFilename),
831  1382449759
832  );
833  }
834  fclose($fileHandle);
835  }
836  $packageStatesCode = "<?php\n$fileDescription\nreturn " . ArrayUtility::arrayExport($this->packageStatesConfiguration) . ";\n";
837  GeneralUtility::writeFile($this->packageStatesPathAndFilename, $packageStatesCode, true);
838 
840 
841  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($this->packageStatesPathAndFilename);
842  }
843 
851  public function isPackageKeyValid($packageKey)
852  {
853  return preg_match(PackageInterface::PATTERN_MATCH_PACKAGEKEY, $packageKey) === 1 || preg_match(PackageInterface::PATTERN_MATCH_EXTENSIONKEY, $packageKey) === 1;
854  }
855 
863  public function getAvailablePackages()
864  {
865  return $this->packages;
866  }
867 
875  public function unregisterPackage(PackageInterface $package)
876  {
877  $packageKey = $package->getPackageKey();
878  if (!$this->isPackageAvailable($packageKey)) {
879  throw new Exception\InvalidPackageStateException('Package "' . $packageKey . '" is not registered.', 1338996142);
880  }
881  $this->unregisterPackageByPackageKey($packageKey);
882  }
883 
893  public function reloadPackageInformation($packageKey)
894  {
895  if (!$this->isPackageAvailable($packageKey)) {
896  throw new Exception\InvalidPackageStateException('Package "' . $packageKey . '" is not registered.', 1436201329);
897  }
898 
900  $package = $this->packages[$packageKey];
901  $packagePath = $package->getPackagePath();
902  $newPackage = new Package($this, $packageKey, $packagePath);
903  $this->packages[$packageKey] = $newPackage;
904  unset($package);
905  }
906 
916  protected function scanPackagesInPath($startPath, array $collectedPackagePaths)
917  {
918  foreach (new \DirectoryIterator($startPath) as $fileInfo) {
919  if (!$fileInfo->isDir()) {
920  continue;
921  }
922  $filename = $fileInfo->getFilename();
923  if ($filename[0] !== '.') {
924  $currentPath = $fileInfo->getPathName();
925  $currentPath = PathUtility::sanitizeTrailingSeparator($currentPath);
926  if ($this->hasComposerManifestFile($currentPath)) {
927  $collectedPackagePaths[$currentPath] = $currentPath;
928  }
929  }
930  }
931  return $collectedPackagePaths;
932  }
933 
941  public function getComposerManifest($manifestPath)
942  {
943  $composerManifest = null;
944  if (file_exists($manifestPath . 'composer.json')) {
945  $json = file_get_contents($manifestPath . 'composer.json');
946  $composerManifest = json_decode($json);
947  if (!$composerManifest instanceof \stdClass) {
948  throw new Exception\InvalidPackageManifestException('The composer.json found for extension "' . basename($manifestPath) . '" is invalid!', 1439555561);
949  }
950  }
951 
952  $extensionManagerConfiguration = $this->getExtensionEmConf($manifestPath);
953  $composerManifest = $this->mapExtensionManagerConfigurationToComposerManifest(
954  basename($manifestPath),
955  $extensionManagerConfiguration,
956  $composerManifest ?: new \stdClass()
957  );
958 
959  return $composerManifest;
960  }
961 
970  protected function getExtensionEmConf($packagePath)
971  {
972  $packageKey = basename($packagePath);
973  $_EXTKEY = $packageKey;
974  $path = $packagePath . 'ext_emconf.php';
975  $EM_CONF = null;
976  if (@file_exists($path)) {
977  include $path;
978  if (is_array($EM_CONF[$_EXTKEY])) {
979  return $EM_CONF[$_EXTKEY];
980  }
981  }
982  throw new Exception\InvalidPackageManifestException('No valid ext_emconf.php file found for package "' . $packageKey . '".', 1360403545);
983  }
984 
994  protected function mapExtensionManagerConfigurationToComposerManifest($packageKey, array $extensionManagerConfiguration, \stdClass $composerManifest)
995  {
996  $this->setComposerManifestValueIfEmpty($composerManifest, 'name', $packageKey);
997  $this->setComposerManifestValueIfEmpty($composerManifest, 'type', 'typo3-cms-extension');
998  $this->setComposerManifestValueIfEmpty($composerManifest, 'description', $extensionManagerConfiguration['title']);
999  $composerManifest->version = $extensionManagerConfiguration['version'];
1000  if (isset($extensionManagerConfiguration['constraints']['depends']) && is_array($extensionManagerConfiguration['constraints']['depends'])) {
1001  $composerManifest->require = new \stdClass();
1002  foreach ($extensionManagerConfiguration['constraints']['depends'] as $requiredPackageKey => $requiredPackageVersion) {
1003  if (!empty($requiredPackageKey)) {
1004  if ($requiredPackageKey === 'typo3') {
1005  // Add implicit dependency to 'core'
1006  $composerManifest->require->core = $requiredPackageVersion;
1007  } elseif ($requiredPackageKey !== 'php') {
1008  // Skip php dependency
1009  $composerManifest->require->{$requiredPackageKey} = $requiredPackageVersion;
1010  }
1011  } else {
1012  throw new Exception\InvalidPackageManifestException(sprintf('The extension "%s" has invalid version constraints in depends section. Extension key is missing!', $packageKey), 1439552058);
1013  }
1014  }
1015  }
1016  if (isset($extensionManagerConfiguration['constraints']['conflicts']) && is_array($extensionManagerConfiguration['constraints']['conflicts'])) {
1017  $composerManifest->conflict = new \stdClass();
1018  foreach ($extensionManagerConfiguration['constraints']['conflicts'] as $conflictingPackageKey => $conflictingPackageVersion) {
1019  if (!empty($conflictingPackageKey)) {
1020  $composerManifest->conflict->$conflictingPackageKey = $conflictingPackageVersion;
1021  } else {
1022  throw new Exception\InvalidPackageManifestException(sprintf('The extension "%s" has invalid version constraints in conflicts section. Extension key is missing!', $packageKey), 1439552059);
1023  }
1024  }
1025  }
1026  if (isset($extensionManagerConfiguration['constraints']['suggests']) && is_array($extensionManagerConfiguration['constraints']['suggests'])) {
1027  $composerManifest->suggest = new \stdClass();
1028  foreach ($extensionManagerConfiguration['constraints']['suggests'] as $suggestedPackageKey => $suggestedPackageVersion) {
1029  if (!empty($suggestedPackageKey)) {
1030  $composerManifest->suggest->$suggestedPackageKey = $suggestedPackageVersion;
1031  } else {
1032  throw new Exception\InvalidPackageManifestException(sprintf('The extension "%s" has invalid version constraints in suggests section. Extension key is missing!', $packageKey), 1439552060);
1033  }
1034  }
1035  }
1036  if (isset($extensionManagerConfiguration['autoload'])) {
1037  $composerManifest->autoload = json_decode(json_encode($extensionManagerConfiguration['autoload']));
1038  }
1039  // composer.json autoload-dev information must be discarded, as it may contain information only available after a composer install
1040  unset($composerManifest->{'autoload-dev'});
1041  if (isset($extensionManagerConfiguration['autoload-dev'])) {
1042  $composerManifest->{'autoload-dev'} = json_decode(json_encode($extensionManagerConfiguration['autoload-dev']));
1043  }
1044 
1045  return $composerManifest;
1046  }
1047 
1054  protected function setComposerManifestValueIfEmpty(\stdClass $manifest, $property, $value)
1055  {
1056  if (empty($manifest->{$property})) {
1057  $manifest->{$property} = $value;
1058  }
1059 
1060  return $manifest;
1061  }
1062 
1074  protected function getDependencyArrayForPackage($packageKey, array &$dependentPackageKeys = [], array $trace = [])
1075  {
1076  if (!isset($this->packages[$packageKey])) {
1077  return null;
1078  }
1079  if (in_array($packageKey, $trace, true) !== false) {
1080  return $dependentPackageKeys;
1081  }
1082  $trace[] = $packageKey;
1083  $dependentPackageConstraints = $this->packages[$packageKey]->getPackageMetaData()->getConstraintsByType(MetaData::CONSTRAINT_TYPE_DEPENDS);
1084  foreach ($dependentPackageConstraints as $constraint) {
1085  if ($constraint instanceof MetaData\PackageConstraint) {
1086  $dependentPackageKey = $constraint->getValue();
1087  if (in_array($dependentPackageKey, $dependentPackageKeys, true) === false && in_array($dependentPackageKey, $trace, true) === false) {
1088  $dependentPackageKeys[] = $dependentPackageKey;
1089  }
1090  $this->getDependencyArrayForPackage($dependentPackageKey, $dependentPackageKeys, $trace);
1091  }
1092  }
1093  return array_reverse($dependentPackageKeys);
1094  }
1095 
1112  protected function getPackageKeyFromManifest($manifest, $packagePath, $packagesBasePath)
1113  {
1114  if (!is_object($manifest)) {
1115  throw new Exception\InvalidPackageManifestException('Invalid composer manifest in package path: ' . $packagePath, 1348146451);
1116  }
1117  if (isset($manifest->type) && substr($manifest->type, 0, 10) === 'typo3-cms-') {
1118  $relativePackagePath = substr($packagePath, strlen($packagesBasePath));
1119  $packageKey = substr($relativePackagePath, strpos($relativePackagePath, '/') + 1, -1);
1120  return preg_replace('/[^A-Za-z0-9._-]/', '', $packageKey);
1121  } else {
1122  $packageKey = str_replace('/', '.', $manifest->name);
1123  return preg_replace('/[^A-Za-z0-9.]/', '', $packageKey);
1124  }
1125  }
1126 }
injectCoreCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache)
registerPackagesFromConfiguration($registerOnlyNewPackages=false)
registerTransientClassLoadingInformationForPackage(PackageInterface $package)
injectDependencyResolver(DependencyResolver $dependencyResolver)
static arrayExport(array $array=[], $level=0)
setComposerManifestValueIfEmpty(\stdClass $manifest, $property, $value)
getPackageKeyFromManifest($manifest, $packagePath, $packagesBasePath)
getDependencyArrayForPackage($packageKey, array &$dependentPackageKeys=[], array $trace=[])
static rmdir($path, $removeNonEmpty=false)
$EM_CONF[$_EXTKEY]
Definition: ext_emconf.php:2
mapExtensionManagerConfigurationToComposerManifest($packageKey, array $extensionManagerConfiguration, \stdClass $composerManifest)
scanPackagesInPath($startPath, array $collectedPackagePaths)
unregisterPackage(PackageInterface $package)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static writeFile($file, $content, $changePermissions=false)
registerPackage(PackageInterface $package, $sortAndSave=true)
static sanitizeTrailingSeparator($path, $separator='/')