TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 
17 use Symfony\Component\Finder\Finder;
18 use Symfony\Component\Finder\SplFileInfo;
27 
33 {
38 
42  protected $coreCache;
43 
47  protected $cacheIdentifier;
48 
52  protected $packagesBasePaths = [];
53 
57  protected $packageAliasMap = [];
58 
62  protected $runtimeActivatedPackages = [];
63 
68  protected $packagesBasePath = PATH_site;
69 
74  protected $packages = [];
75 
81 
86  protected $activePackages = [];
87 
92 
98 
102  public function __construct()
103  {
104  $this->packageStatesPathAndFilename = PATH_typo3conf . 'PackageStates.php';
105  }
106 
110  public function injectCoreCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache)
111  {
112  $this->coreCache = $coreCache;
113  }
114 
119  {
120  $this->dependencyResolver = $dependencyResolver;
121  }
122 
128  public function initialize()
129  {
130  try {
132  } catch (Exception\PackageManagerCacheUnavailableException $exception) {
133  $this->loadPackageStates();
134  $this->initializePackageObjects();
136  $this->saveToPackageCache();
137  }
138  }
139 
143  protected function getCacheIdentifier()
144  {
145  if ($this->cacheIdentifier === null) {
146  $mTime = @filemtime($this->packageStatesPathAndFilename);
147  if ($mTime !== false) {
148  $this->cacheIdentifier = md5($this->packageStatesPathAndFilename . $mTime);
149  } else {
150  $this->cacheIdentifier = null;
151  }
152  }
153  return $this->cacheIdentifier;
154  }
155 
159  protected function getCacheEntryIdentifier()
160  {
162  return $cacheIdentifier !== null ? 'PackageManager_' . $cacheIdentifier : null;
163  }
164 
168  protected function saveToPackageCache()
169  {
170  $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
171  if ($cacheEntryIdentifier !== null && !$this->coreCache->has($cacheEntryIdentifier)) {
172  // Package objects get their own cache entry, so PHP does not have to parse the serialized string
173  $packageObjectsCacheEntryIdentifier = StringUtility::getUniqueId('PackageObjects_');
174  // Build cache file
175  $packageCache = [
176  'packageStatesConfiguration' => $this->packageStatesConfiguration,
177  'packageAliasMap' => $this->packageAliasMap,
178  'loadedExtArray' => $GLOBALS['TYPO3_LOADED_EXT'],
179  'composerNameToPackageKeyMap' => $this->composerNameToPackageKeyMap,
180  'packageObjectsCacheEntryIdentifier' => $packageObjectsCacheEntryIdentifier
181  ];
182  $this->coreCache->set($packageObjectsCacheEntryIdentifier, serialize($this->packages));
183  $this->coreCache->set(
184  $cacheEntryIdentifier,
185  'return ' . PHP_EOL . var_export($packageCache, true) . ';'
186  );
187  }
188  }
189 
196  {
197  $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
198  if ($cacheEntryIdentifier === null || !$this->coreCache->has($cacheEntryIdentifier) || !($packageCache = $this->coreCache->requireOnce($cacheEntryIdentifier))) {
199  throw new Exception\PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883342);
200  }
201  $this->packageStatesConfiguration = $packageCache['packageStatesConfiguration'];
202  if ($this->packageStatesConfiguration['version'] < 5) {
203  throw new Exception\PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883341);
204  }
205  $this->packageAliasMap = $packageCache['packageAliasMap'];
206  $this->composerNameToPackageKeyMap = $packageCache['composerNameToPackageKeyMap'];
207  $GLOBALS['TYPO3_LOADED_EXT'] = $packageCache['loadedExtArray'];
208  $GLOBALS['TYPO3_currentPackageManager'] = $this;
209  // Strip off PHP Tags from Php Cache Frontend
210  $packageObjects = substr(substr($this->coreCache->get($packageCache['packageObjectsCacheEntryIdentifier']), 6), 0, -2);
211  $this->packages = unserialize($packageObjects);
212  unset($GLOBALS['TYPO3_currentPackageManager']);
213  }
214 
222  protected function loadPackageStates()
223  {
224  $forcePackageStatesRewrite = false;
225  $this->packageStatesConfiguration = @include($this->packageStatesPathAndFilename) ?: [];
226  if (!isset($this->packageStatesConfiguration['version']) || $this->packageStatesConfiguration['version'] < 4) {
227  $this->packageStatesConfiguration = [];
228  } elseif ($this->packageStatesConfiguration['version'] === 4) {
229  // Convert to v5 format which only includes a list of active packages.
230  // Deprecated since version 8, will be removed in version 9.
231  $activePackages = [];
232  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $packageConfiguration) {
233  if ($packageConfiguration['state'] !== 'active') {
234  continue;
235  }
236  $activePackages[$packageKey] = ['packagePath' => $packageConfiguration['packagePath']];
237  }
238  $this->packageStatesConfiguration['packages'] = $activePackages;
239  $this->packageStatesConfiguration['version'] = 5;
240  $forcePackageStatesRewrite = true;
241  }
242  if ($this->packageStatesConfiguration !== []) {
243  $this->registerPackagesFromConfiguration($this->packageStatesConfiguration['packages'], false, $forcePackageStatesRewrite);
244  } else {
245  throw new Exception\PackageStatesUnavailableException('The PackageStates.php file is either corrupt or unavailable.', 1381507733);
246  }
247  }
248 
256  protected function initializePackageObjects()
257  {
258  $requiredPackages = [];
259  $activePackages = [];
260  foreach ($this->packages as $packageKey => $package) {
261  if ($package->isProtected()) {
262  $requiredPackages[$packageKey] = $package;
263  }
264  if (isset($this->packageStatesConfiguration['packages'][$packageKey])) {
265  $activePackages[$packageKey] = $package;
266  }
267  }
268  $previousActivePackages = $activePackages;
269  $activePackages = array_merge($requiredPackages, $activePackages);
270 
271  if ($activePackages != $previousActivePackages) {
272  foreach ($requiredPackages as $requiredPackageKey => $package) {
273  $this->registerActivePackage($package);
274  }
275  $this->sortAndSavePackageStates();
276  }
277  }
278 
282  protected function registerActivePackage(PackageInterface $package)
283  {
284  // reset the active packages so they are rebuilt.
285  $this->activePackages = [];
286  $this->packageStatesConfiguration['packages'][$package->getPackageKey()] = ['packagePath' => str_replace($this->packagesBasePath, '', $package->getPackagePath())];
287  }
288 
295  {
296  $loadedExtObj = new \TYPO3\CMS\Core\Compatibility\LoadedExtensionsArray($this);
297  $GLOBALS['TYPO3_LOADED_EXT'] = $loadedExtObj->toArray();
298  }
299 
306  public function scanAvailablePackages()
307  {
308  $packagePaths = $this->scanPackagePathsForExtensions();
309  $packages = [];
310  foreach ($packagePaths as $packageKey => $packagePath) {
311  try {
312  $composerManifest = $this->getComposerManifest($packagePath);
313  $packageKey = $this->getPackageKeyFromManifest($composerManifest, $packagePath);
314  $this->composerNameToPackageKeyMap[strtolower($composerManifest->name)] = $packageKey;
315  $packages[$packageKey] = ['packagePath' => str_replace($this->packagesBasePath, '', $packagePath)];
316  } catch (Exception\MissingPackageManifestException $exception) {
317  if (!$this->isPackageKeyValid($packageKey)) {
318  continue;
319  }
320  } catch (Exception\InvalidPackageKeyException $exception) {
321  continue;
322  }
323  }
324 
325  $registerOnlyNewPackages = !empty($this->packages);
326  $this->registerPackagesFromConfiguration($packages, $registerOnlyNewPackages);
327  }
328 
335  protected function registerPackageDuringRuntime($packageKey)
336  {
337  $packagePaths = $this->scanPackagePathsForExtensions();
338  $packagePath = $packagePaths[$packageKey];
339  $composerManifest = $this->getComposerManifest($packagePath);
340  $packageKey = $this->getPackageKeyFromManifest($composerManifest, $packagePath);
341  $this->composerNameToPackageKeyMap[strtolower($composerManifest->name)] = $packageKey;
342  $packagePath = PathUtility::sanitizeTrailingSeparator($packagePath);
343  $package = new Package($this, $packageKey, $packagePath);
344  $this->registerPackage($package);
345  return $package;
346  }
347 
353  protected function scanPackagePathsForExtensions()
354  {
355  $collectedExtensionPaths = [];
356  foreach ($this->getPackageBasePaths() as $packageBasePath) {
357  // Only add the extension if we have an EMCONF and the extension is not yet registered.
358  // This is crucial in order to allow overriding of system extension by local extensions
359  // and strongly depends on the order of paths defined in $this->packagesBasePaths.
360  $finder = new Finder();
361  $finder
362  ->name('ext_emconf.php')
363  ->followLinks()
364  ->depth(0)
365  ->ignoreUnreadableDirs()
366  ->in($packageBasePath);
367 
369  foreach ($finder as $fileInfo) {
370  $path = dirname($fileInfo->getPathname());
371  $extensionName = basename($path);
372  // Fix Windows backslashes
373  // we can't use GeneralUtility::fixWindowsFilePath as we have to keep double slashes for Unit Tests (vfs://)
374  $currentPath = str_replace('\\', '/', $path) . '/';
375  if (!isset($collectedExtensionPaths[$extensionName])) {
376  $collectedExtensionPaths[$extensionName] = $currentPath;
377  }
378  }
379  }
380  return $collectedExtensionPaths;
381  }
382 
392  protected function registerPackagesFromConfiguration(array $packages, $registerOnlyNewPackages = false, $packageStatesHasChanged = false)
393  {
394  foreach ($packages as $packageKey => $stateConfiguration) {
395  if ($registerOnlyNewPackages && $this->isPackageAvailable($packageKey)) {
396  continue;
397  }
398 
399  if (!isset($stateConfiguration['packagePath'])) {
400  $this->unregisterPackageByPackageKey($packageKey);
401  $packageStatesHasChanged = true;
402  continue;
403  }
404 
405  try {
406  $packagePath = PathUtility::sanitizeTrailingSeparator($this->packagesBasePath . $stateConfiguration['packagePath']);
407  $package = new Package($this, $packageKey, $packagePath);
408  } catch (Exception\InvalidPackagePathException $exception) {
409  $this->unregisterPackageByPackageKey($packageKey);
410  $packageStatesHasChanged = true;
411  continue;
412  } catch (Exception\InvalidPackageKeyException $exception) {
413  $this->unregisterPackageByPackageKey($packageKey);
414  $packageStatesHasChanged = true;
415  continue;
416  } catch (Exception\InvalidPackageManifestException $exception) {
417  $this->unregisterPackageByPackageKey($packageKey);
418  $packageStatesHasChanged = true;
419  continue;
420  }
421 
422  $this->registerPackage($package);
423  }
424  if ($packageStatesHasChanged) {
425  $this->sortAndSavePackageStates();
426  }
427  }
428 
437  public function registerPackage(PackageInterface $package)
438  {
439  $packageKey = $package->getPackageKey();
440  if ($this->isPackageAvailable($packageKey)) {
441  throw new Exception\InvalidPackageStateException('Package "' . $packageKey . '" is already registered.', 1338996122);
442  }
443 
444  $this->packages[$packageKey] = $package;
445 
446  if ($package instanceof PackageInterface) {
447  foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
448  $this->packageAliasMap[strtolower($packageToReplace)] = $package->getPackageKey();
449  }
450  }
451  return $package;
452  }
453 
460  protected function unregisterPackageByPackageKey($packageKey)
461  {
462  try {
463  $package = $this->getPackage($packageKey);
464  if ($package instanceof PackageInterface) {
465  foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
466  unset($this->packageAliasMap[strtolower($packageToReplace)]);
467  }
468  }
469  } catch (Exception\UnknownPackageException $e) {
470  }
471  unset($this->packages[$packageKey]);
472  unset($this->packageStatesConfiguration['packages'][$packageKey]);
473  }
474 
481  public function getPackageKeyFromComposerName($composerName)
482  {
483  if (isset($this->packageAliasMap[$composerName])) {
484  return $this->packageAliasMap[$composerName];
485  }
486  $lowercasedComposerName = strtolower($composerName);
487  if (isset($this->composerNameToPackageKeyMap[$lowercasedComposerName])) {
488  return $this->composerNameToPackageKeyMap[$lowercasedComposerName];
489  } else {
490  return $composerName;
491  }
492  }
493 
503  public function getPackage($packageKey)
504  {
505  if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
506  $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
507  }
508  if (!$this->isPackageAvailable($packageKey)) {
509  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);
510  }
511  return $this->packages[$packageKey];
512  }
513 
522  public function isPackageAvailable($packageKey)
523  {
524  if (isset($this->packageAliasMap[$lowercasedPackageKey = strtolower($packageKey)])) {
525  $packageKey = $this->packageAliasMap[$lowercasedPackageKey];
526  }
527  return isset($this->packages[$packageKey]);
528  }
529 
537  public function isPackageActive($packageKey)
538  {
539  return isset($this->runtimeActivatedPackages[$packageKey]) || isset($this->packageStatesConfiguration['packages'][$packageKey]);
540  }
541 
550  public function deactivatePackage($packageKey)
551  {
552  $packagesWithDependencies = $this->sortActivePackagesByDependencies();
553 
554  foreach ($packagesWithDependencies as $packageStateKey => $packageStateConfiguration) {
555  if ($packageKey === $packageStateKey || empty($packageStateConfiguration['dependencies'])) {
556  continue;
557  }
558  if (in_array($packageKey, $packageStateConfiguration['dependencies'], true)) {
559  $this->deactivatePackage($packageStateKey);
560  }
561  }
562 
563  if (!$this->isPackageActive($packageKey)) {
564  return;
565  }
566 
567  $package = $this->getPackage($packageKey);
568  if ($package->isProtected()) {
569  throw new Exception\ProtectedPackageKeyException('The package "' . $packageKey . '" is protected and cannot be deactivated.', 1308662891);
570  }
571 
572  $this->activePackages = [];
573  unset($this->packageStatesConfiguration['packages'][$packageKey]);
574  $this->sortAndSavePackageStates();
575  }
576 
580  public function activatePackage($packageKey)
581  {
582  $package = $this->getPackage($packageKey);
584 
585  if ($this->isPackageActive($packageKey)) {
586  return;
587  }
588 
589  $this->registerActivePackage($package);
590  $this->sortAndSavePackageStates();
591  }
592 
599  public function activatePackageDuringRuntime($packageKey)
600  {
601  $package = $this->registerPackageDuringRuntime($packageKey);
602  $this->runtimeActivatedPackages[$package->getPackageKey()] = $package;
603  if (!isset($GLOBALS['TYPO3_LOADED_EXT'][$package->getPackageKey()])) {
604  $loadedExtArrayElement = new LoadedExtensionArrayElement($package);
605  $GLOBALS['TYPO3_LOADED_EXT'][$package->getPackageKey()] = $loadedExtArrayElement->toArray();
606  }
608  }
609 
615  {
617  return;
618  }
619  ClassLoadingInformation::registerTransientClassLoadingInformationForPackage($package);
620  }
621 
631  public function deletePackage($packageKey)
632  {
633  if (!$this->isPackageAvailable($packageKey)) {
634  throw new Exception\UnknownPackageException('Package "' . $packageKey . '" is not available and cannot be removed.', 1166543253);
635  }
636 
637  $package = $this->getPackage($packageKey);
638  if ($package->isProtected()) {
639  throw new Exception\ProtectedPackageKeyException('The package "' . $packageKey . '" is protected and cannot be removed.', 1220722120);
640  }
641 
642  if ($this->isPackageActive($packageKey)) {
643  $this->deactivatePackage($packageKey);
644  }
645 
646  $this->unregisterPackage($package);
647  $this->sortAndSavePackageStates();
648 
649  $packagePath = $package->getPackagePath();
650  $deletion = GeneralUtility::rmdir($packagePath, true);
651  if ($deletion === false) {
652  throw new Exception('Please check file permissions. The directory "' . $packagePath . '" for package "' . $packageKey . '" could not be removed.', 1301491089);
653  }
654  }
655 
664  public function getActivePackages()
665  {
666  if (empty($this->activePackages)) {
667  if (!empty($this->packageStatesConfiguration['packages'])) {
668  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $packageConfig) {
669  $this->activePackages[$packageKey] = $this->getPackage($packageKey);
670  }
671  }
672  }
673  return array_merge($this->activePackages, $this->runtimeActivatedPackages);
674  }
675 
683  protected function sortActivePackagesByDependencies()
684  {
685  $packagesWithDependencies = $this->resolvePackageDependencies($this->packageStatesConfiguration['packages']);
686 
687  // sort the packages by key at first, so we get a stable sorting of "equivalent" packages afterwards
688  ksort($packagesWithDependencies);
689  $sortedPackageKeys = $this->dependencyResolver->sortPackageStatesConfigurationByDependency($packagesWithDependencies);
690 
691  // Reorder the packages according to the loading order
692  $this->packageStatesConfiguration['packages'] = [];
693  foreach ($sortedPackageKeys as $packageKey) {
694  $this->registerActivePackage($this->packages[$packageKey]);
695  }
696  return $packagesWithDependencies;
697  }
698 
707  protected function resolvePackageDependencies($packageConfig)
708  {
709  $packagesWithDependencies = [];
710  foreach ($packageConfig as $packageKey => $_) {
711  $packagesWithDependencies[$packageKey]['dependencies'] = $this->getDependencyArrayForPackage($packageKey);
712  $packagesWithDependencies[$packageKey]['suggestions'] = $this->getSuggestionArrayForPackage($packageKey);
713  }
714  return $packagesWithDependencies;
715  }
716 
723  protected function getSuggestionArrayForPackage($packageKey)
724  {
725  if (!isset($this->packages[$packageKey])) {
726  return null;
727  }
728  $suggestedPackageKeys = [];
729  $suggestedPackageConstraints = $this->packages[$packageKey]->getPackageMetaData()->getConstraintsByType(MetaData::CONSTRAINT_TYPE_SUGGESTS);
730  foreach ($suggestedPackageConstraints as $constraint) {
731  if ($constraint instanceof MetaData\PackageConstraint) {
732  $suggestedPackageKey = $constraint->getValue();
733  if (isset($this->packages[$suggestedPackageKey])) {
734  $suggestedPackageKeys[] = $suggestedPackageKey;
735  }
736  }
737  }
738  return array_reverse($suggestedPackageKeys);
739  }
740 
747  protected function sortAndSavePackageStates()
748  {
750 
751  $this->packageStatesConfiguration['version'] = 5;
752 
753  $fileDescription = "# PackageStates.php\n\n";
754  $fileDescription .= "# This file is maintained by TYPO3's package management. Although you can edit it\n";
755  $fileDescription .= "# manually, you should rather use the extension manager for maintaining packages.\n";
756  $fileDescription .= "# This file will be regenerated automatically if it doesn't exist. Deleting this file\n";
757  $fileDescription .= "# should, however, never become necessary if you use the package commands.\n";
758 
759  if (!@is_writable($this->packageStatesPathAndFilename)) {
760  // If file does not exists try to create it
761  $fileHandle = @fopen($this->packageStatesPathAndFilename, 'x');
762  if (!$fileHandle) {
763  throw new Exception\PackageStatesFileNotWritableException(
764  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),
765  1382449759
766  );
767  }
768  fclose($fileHandle);
769  }
770  $packageStatesCode = "<?php\n$fileDescription\nreturn " . ArrayUtility::arrayExport($this->packageStatesConfiguration) . ";\n";
771  GeneralUtility::writeFile($this->packageStatesPathAndFilename, $packageStatesCode, true);
772 
774 
775  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($this->packageStatesPathAndFilename);
776  }
777 
785  public function isPackageKeyValid($packageKey)
786  {
787  return preg_match(PackageInterface::PATTERN_MATCH_PACKAGEKEY, $packageKey) === 1 || preg_match(PackageInterface::PATTERN_MATCH_EXTENSIONKEY, $packageKey) === 1;
788  }
789 
797  public function getAvailablePackages()
798  {
799  return $this->packages;
800  }
801 
809  public function unregisterPackage(PackageInterface $package)
810  {
811  $packageKey = $package->getPackageKey();
812  if (!$this->isPackageAvailable($packageKey)) {
813  throw new Exception\InvalidPackageStateException('Package "' . $packageKey . '" is not registered.', 1338996142);
814  }
815  $this->unregisterPackageByPackageKey($packageKey);
816  }
817 
827  public function reloadPackageInformation($packageKey)
828  {
829  if (!$this->isPackageAvailable($packageKey)) {
830  throw new Exception\InvalidPackageStateException('Package "' . $packageKey . '" is not registered.', 1436201329);
831  }
832 
834  $package = $this->packages[$packageKey];
835  $packagePath = $package->getPackagePath();
836  $newPackage = new Package($this, $packageKey, $packagePath);
837  $this->packages[$packageKey] = $newPackage;
838  unset($package);
839  }
840 
848  public function getComposerManifest($manifestPath)
849  {
850  $composerManifest = null;
851  if (file_exists($manifestPath . 'composer.json')) {
852  $json = file_get_contents($manifestPath . 'composer.json');
853  $composerManifest = json_decode($json);
854  if (!$composerManifest instanceof \stdClass) {
855  throw new Exception\InvalidPackageManifestException('The composer.json found for extension "' . basename($manifestPath) . '" is invalid!', 1439555561);
856  }
857  }
858 
859  $extensionManagerConfiguration = $this->getExtensionEmConf($manifestPath);
860  $composerManifest = $this->mapExtensionManagerConfigurationToComposerManifest(
861  basename($manifestPath),
862  $extensionManagerConfiguration,
863  $composerManifest ?: new \stdClass()
864  );
865 
866  return $composerManifest;
867  }
868 
877  protected function getExtensionEmConf($packagePath)
878  {
879  $packageKey = basename($packagePath);
880  $_EXTKEY = $packageKey;
881  $path = $packagePath . 'ext_emconf.php';
882  $EM_CONF = null;
883  if (@file_exists($path)) {
884  include $path;
885  if (is_array($EM_CONF[$_EXTKEY])) {
886  return $EM_CONF[$_EXTKEY];
887  }
888  }
889  throw new Exception\InvalidPackageManifestException('No valid ext_emconf.php file found for package "' . $packageKey . '".', 1360403545);
890  }
891 
901  protected function mapExtensionManagerConfigurationToComposerManifest($packageKey, array $extensionManagerConfiguration, \stdClass $composerManifest)
902  {
903  $this->setComposerManifestValueIfEmpty($composerManifest, 'name', $packageKey);
904  $this->setComposerManifestValueIfEmpty($composerManifest, 'type', 'typo3-cms-extension');
905  $this->setComposerManifestValueIfEmpty($composerManifest, 'description', $extensionManagerConfiguration['title']);
906  $composerManifest->version = $extensionManagerConfiguration['version'];
907  if (isset($extensionManagerConfiguration['constraints']['depends']) && is_array($extensionManagerConfiguration['constraints']['depends'])) {
908  $composerManifest->require = new \stdClass();
909  foreach ($extensionManagerConfiguration['constraints']['depends'] as $requiredPackageKey => $requiredPackageVersion) {
910  if (!empty($requiredPackageKey)) {
911  if ($requiredPackageKey === 'typo3') {
912  // Add implicit dependency to 'core'
913  $composerManifest->require->core = $requiredPackageVersion;
914  } elseif ($requiredPackageKey !== 'php') {
915  // Skip php dependency
916  $composerManifest->require->{$requiredPackageKey} = $requiredPackageVersion;
917  }
918  } else {
919  throw new Exception\InvalidPackageManifestException(sprintf('The extension "%s" has invalid version constraints in depends section. Extension key is missing!', $packageKey), 1439552058);
920  }
921  }
922  }
923  if (isset($extensionManagerConfiguration['constraints']['conflicts']) && is_array($extensionManagerConfiguration['constraints']['conflicts'])) {
924  $composerManifest->conflict = new \stdClass();
925  foreach ($extensionManagerConfiguration['constraints']['conflicts'] as $conflictingPackageKey => $conflictingPackageVersion) {
926  if (!empty($conflictingPackageKey)) {
927  $composerManifest->conflict->$conflictingPackageKey = $conflictingPackageVersion;
928  } else {
929  throw new Exception\InvalidPackageManifestException(sprintf('The extension "%s" has invalid version constraints in conflicts section. Extension key is missing!', $packageKey), 1439552059);
930  }
931  }
932  }
933  if (isset($extensionManagerConfiguration['constraints']['suggests']) && is_array($extensionManagerConfiguration['constraints']['suggests'])) {
934  $composerManifest->suggest = new \stdClass();
935  foreach ($extensionManagerConfiguration['constraints']['suggests'] as $suggestedPackageKey => $suggestedPackageVersion) {
936  if (!empty($suggestedPackageKey)) {
937  $composerManifest->suggest->$suggestedPackageKey = $suggestedPackageVersion;
938  } else {
939  throw new Exception\InvalidPackageManifestException(sprintf('The extension "%s" has invalid version constraints in suggests section. Extension key is missing!', $packageKey), 1439552060);
940  }
941  }
942  }
943  if (isset($extensionManagerConfiguration['autoload'])) {
944  $composerManifest->autoload = json_decode(json_encode($extensionManagerConfiguration['autoload']));
945  }
946  // composer.json autoload-dev information must be discarded, as it may contain information only available after a composer install
947  unset($composerManifest->{'autoload-dev'});
948  if (isset($extensionManagerConfiguration['autoload-dev'])) {
949  $composerManifest->{'autoload-dev'} = json_decode(json_encode($extensionManagerConfiguration['autoload-dev']));
950  }
951 
952  return $composerManifest;
953  }
954 
961  protected function setComposerManifestValueIfEmpty(\stdClass $manifest, $property, $value)
962  {
963  if (empty($manifest->{$property})) {
964  $manifest->{$property} = $value;
965  }
966 
967  return $manifest;
968  }
969 
981  protected function getDependencyArrayForPackage($packageKey, array &$dependentPackageKeys = [], array $trace = [])
982  {
983  if (!isset($this->packages[$packageKey])) {
984  return null;
985  }
986  if (in_array($packageKey, $trace, true) !== false) {
987  return $dependentPackageKeys;
988  }
989  $trace[] = $packageKey;
990  $dependentPackageConstraints = $this->packages[$packageKey]->getPackageMetaData()->getConstraintsByType(MetaData::CONSTRAINT_TYPE_DEPENDS);
991  foreach ($dependentPackageConstraints as $constraint) {
992  if ($constraint instanceof MetaData\PackageConstraint) {
993  $dependentPackageKey = $constraint->getValue();
994  if (in_array($dependentPackageKey, $dependentPackageKeys, true) === false && in_array($dependentPackageKey, $trace, true) === false) {
995  $dependentPackageKeys[] = $dependentPackageKey;
996  }
997  $this->getDependencyArrayForPackage($dependentPackageKey, $dependentPackageKeys, $trace);
998  }
999  }
1000  return array_reverse($dependentPackageKeys);
1001  }
1002 
1018  protected function getPackageKeyFromManifest($manifest, $packagePath)
1019  {
1020  if (!is_object($manifest)) {
1021  throw new Exception\InvalidPackageManifestException('Invalid composer manifest in package path: ' . $packagePath, 1348146451);
1022  }
1023  if (isset($manifest->type) && substr($manifest->type, 0, 10) === 'typo3-cms-') {
1024  $packageKey = basename($packagePath);
1025  return preg_replace('/[^A-Za-z0-9._-]/', '', $packageKey);
1026  } else {
1027  $packageKey = str_replace('/', '.', $manifest->name);
1028  return preg_replace('/[^A-Za-z0-9.]/', '', $packageKey);
1029  }
1030  }
1031 
1038  protected function getPackageBasePaths()
1039  {
1040  if (empty($this->packagesBasePaths)) {
1041  // Check if the directory even exists and if it is not empty
1042  if (is_dir(PATH_typo3conf . 'ext') && count(scandir(PATH_typo3conf . 'ext')) > 2) {
1043  $this->packagesBasePaths['local'] = PATH_typo3conf . 'ext/*/';
1044  }
1045  if (is_dir(PATH_typo3 . 'ext') && count(scandir(PATH_typo3 . 'ext')) > 2) {
1046  $this->packagesBasePaths['global'] = PATH_typo3 . 'ext/*/';
1047  }
1048  $this->packagesBasePaths['system'] = PATH_typo3 . 'sysext/*/';
1049  }
1050  return $this->packagesBasePaths;
1051  }
1052 }
injectDependencyResolver(DependencyResolver $dependencyResolver)
static rmdir($path, $removeNonEmpty=false)
getDependencyArrayForPackage($packageKey, array &$dependentPackageKeys=[], array $trace=[])
registerActivePackage(PackageInterface $package)
getPackageKeyFromManifest($manifest, $packagePath)
mapExtensionManagerConfigurationToComposerManifest($packageKey, array $extensionManagerConfiguration,\stdClass $composerManifest)
static writeFile($file, $content, $changePermissions=false)
$EM_CONF[$_EXTKEY]
registerPackagesFromConfiguration(array $packages, $registerOnlyNewPackages=false, $packageStatesHasChanged=false)
unregisterPackage(PackageInterface $package)
injectCoreCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache)
registerPackage(PackageInterface $package)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
registerTransientClassLoadingInformationForPackage(PackageInterface $package)
setComposerManifestValueIfEmpty(\stdClass $manifest, $property, $value)
static sanitizeTrailingSeparator($path, $separator= '/')