TYPO3 CMS  TYPO3_8-7
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 
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 
79  protected $availablePackagesScanned = false;
80 
86 
91  protected $activePackages = [];
92 
97 
103 
107  public function __construct()
108  {
109  $this->packageStatesPathAndFilename = PATH_typo3conf . 'PackageStates.php';
110  }
111 
115  public function injectCoreCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache)
116  {
117  $this->coreCache = $coreCache;
118  }
119 
124  {
125  $this->dependencyResolver = $dependencyResolver;
126  }
127 
131  public function initialize()
132  {
133  try {
135  } catch (Exception\PackageManagerCacheUnavailableException $exception) {
136  $this->loadPackageStates();
137  $this->initializePackageObjects();
139  $this->saveToPackageCache();
140  }
141  }
142 
146  protected function getCacheIdentifier()
147  {
148  if ($this->cacheIdentifier === null) {
149  $mTime = @filemtime($this->packageStatesPathAndFilename);
150  if ($mTime !== false) {
151  $this->cacheIdentifier = md5($this->packageStatesPathAndFilename . $mTime);
152  } else {
153  $this->cacheIdentifier = null;
154  }
155  }
156  return $this->cacheIdentifier;
157  }
158 
162  protected function getCacheEntryIdentifier()
163  {
165  return $cacheIdentifier !== null ? 'PackageManager_' . $cacheIdentifier : null;
166  }
167 
171  protected function saveToPackageCache()
172  {
173  $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
174  if ($cacheEntryIdentifier !== null && !$this->coreCache->has($cacheEntryIdentifier)) {
175  // Package objects get their own cache entry, so PHP does not have to parse the serialized string
176  $packageObjectsCacheEntryIdentifier = StringUtility::getUniqueId('PackageObjects_');
177  // Build cache file
178  $packageCache = [
179  'packageStatesConfiguration' => $this->packageStatesConfiguration,
180  'packageAliasMap' => $this->packageAliasMap,
181  'loadedExtArray' => $GLOBALS['TYPO3_LOADED_EXT'],
182  'composerNameToPackageKeyMap' => $this->composerNameToPackageKeyMap,
183  'packageObjectsCacheEntryIdentifier' => $packageObjectsCacheEntryIdentifier
184  ];
185  $this->coreCache->set($packageObjectsCacheEntryIdentifier, serialize($this->packages));
186  $this->coreCache->set(
187  $cacheEntryIdentifier,
188  'return ' . PHP_EOL . var_export($packageCache, true) . ';'
189  );
190  }
191  }
192 
199  {
200  $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
201  if ($cacheEntryIdentifier === null || !$this->coreCache->has($cacheEntryIdentifier) || !($packageCache = $this->coreCache->requireOnce($cacheEntryIdentifier))) {
202  throw new Exception\PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883342);
203  }
204  $this->packageStatesConfiguration = $packageCache['packageStatesConfiguration'];
205  if ($this->packageStatesConfiguration['version'] < 5) {
206  throw new Exception\PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883341);
207  }
208  $this->packageAliasMap = $packageCache['packageAliasMap'];
209  $this->composerNameToPackageKeyMap = $packageCache['composerNameToPackageKeyMap'];
210  $GLOBALS['TYPO3_LOADED_EXT'] = $packageCache['loadedExtArray'];
211  $GLOBALS['TYPO3_currentPackageManager'] = $this;
212  // Strip off PHP Tags from Php Cache Frontend
213  $packageObjects = substr(substr($this->coreCache->get($packageCache['packageObjectsCacheEntryIdentifier']), 6), 0, -2);
214  $this->packages = unserialize($packageObjects);
215  unset($GLOBALS['TYPO3_currentPackageManager']);
216  }
217 
224  protected function loadPackageStates()
225  {
226  $forcePackageStatesRewrite = false;
227  $this->packageStatesConfiguration = @include($this->packageStatesPathAndFilename) ?: [];
228  if (!isset($this->packageStatesConfiguration['version']) || $this->packageStatesConfiguration['version'] < 4) {
229  $this->packageStatesConfiguration = [];
230  } elseif ($this->packageStatesConfiguration['version'] === 4) {
231  // Convert to v5 format which only includes a list of active packages.
232  // Deprecated since version 8, will be removed in version 9.
233  $activePackages = [];
234  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $packageConfiguration) {
235  if ($packageConfiguration['state'] !== 'active') {
236  continue;
237  }
238  $activePackages[$packageKey] = ['packagePath' => $packageConfiguration['packagePath']];
239  }
240  $this->packageStatesConfiguration['packages'] = $activePackages;
241  $this->packageStatesConfiguration['version'] = 5;
242  $forcePackageStatesRewrite = true;
243  }
244  if ($this->packageStatesConfiguration !== []) {
245  $this->registerPackagesFromConfiguration($this->packageStatesConfiguration['packages'], false, $forcePackageStatesRewrite);
246  } else {
247  throw new Exception\PackageStatesUnavailableException('The PackageStates.php file is either corrupt or unavailable.', 1381507733);
248  }
249  }
250 
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 
293  {
294  $loadedExtObj = new \TYPO3\CMS\Core\Compatibility\LoadedExtensionsArray($this);
295  $GLOBALS['TYPO3_LOADED_EXT'] = $loadedExtObj->toArray();
296  }
297 
302  public function scanAvailablePackages()
303  {
304  $packagePaths = $this->scanPackagePathsForExtensions();
305  $packages = [];
306  foreach ($packagePaths as $packageKey => $packagePath) {
307  try {
308  $composerManifest = $this->getComposerManifest($packagePath);
309  $packageKey = $this->getPackageKeyFromManifest($composerManifest, $packagePath);
310  $this->composerNameToPackageKeyMap[strtolower($composerManifest->name)] = $packageKey;
311  $packages[$packageKey] = ['packagePath' => str_replace($this->packagesBasePath, '', $packagePath)];
312  } catch (Exception\MissingPackageManifestException $exception) {
313  if (!$this->isPackageKeyValid($packageKey)) {
314  continue;
315  }
316  } catch (Exception\InvalidPackageKeyException $exception) {
317  continue;
318  }
319  }
320 
321  $this->availablePackagesScanned = true;
322  $registerOnlyNewPackages = !empty($this->packages);
323  $this->registerPackagesFromConfiguration($packages, $registerOnlyNewPackages);
324  }
325 
332  protected function registerPackageDuringRuntime($packageKey)
333  {
334  $packagePaths = $this->scanPackagePathsForExtensions();
335  $packagePath = $packagePaths[$packageKey];
336  $composerManifest = $this->getComposerManifest($packagePath);
337  $packageKey = $this->getPackageKeyFromManifest($composerManifest, $packagePath);
338  $this->composerNameToPackageKeyMap[strtolower($composerManifest->name)] = $packageKey;
339  $packagePath = PathUtility::sanitizeTrailingSeparator($packagePath);
340  $package = new Package($this, $packageKey, $packagePath);
341  $this->registerPackage($package);
342  return $package;
343  }
344 
350  protected function scanPackagePathsForExtensions()
351  {
352  $collectedExtensionPaths = [];
353  foreach ($this->getPackageBasePaths() as $packageBasePath) {
354  // Only add the extension if we have an EMCONF and the extension is not yet registered.
355  // This is crucial in order to allow overriding of system extension by local extensions
356  // and strongly depends on the order of paths defined in $this->packagesBasePaths.
357  $finder = new Finder();
358  $finder
359  ->name('ext_emconf.php')
360  ->followLinks()
361  ->depth(0)
362  ->ignoreUnreadableDirs()
363  ->in($packageBasePath);
364 
366  foreach ($finder as $fileInfo) {
367  $path = dirname($fileInfo->getPathname());
368  $extensionName = basename($path);
369  // Fix Windows backslashes
370  // we can't use GeneralUtility::fixWindowsFilePath as we have to keep double slashes for Unit Tests (vfs://)
371  $currentPath = str_replace('\\', '/', $path) . '/';
372  if (!isset($collectedExtensionPaths[$extensionName])) {
373  $collectedExtensionPaths[$extensionName] = $currentPath;
374  }
375  }
376  }
377  return $collectedExtensionPaths;
378  }
379 
389  protected function registerPackagesFromConfiguration(array $packages, $registerOnlyNewPackages = false, $packageStatesHasChanged = false)
390  {
391  foreach ($packages as $packageKey => $stateConfiguration) {
392  if ($registerOnlyNewPackages && $this->isPackageRegistered($packageKey)) {
393  continue;
394  }
395 
396  if (!isset($stateConfiguration['packagePath'])) {
397  $this->unregisterPackageByPackageKey($packageKey);
398  $packageStatesHasChanged = true;
399  continue;
400  }
401 
402  try {
403  $packagePath = PathUtility::sanitizeTrailingSeparator($this->packagesBasePath . $stateConfiguration['packagePath']);
404  $package = new Package($this, $packageKey, $packagePath);
405  } catch (Exception\InvalidPackagePathException $exception) {
406  $this->unregisterPackageByPackageKey($packageKey);
407  $packageStatesHasChanged = true;
408  continue;
409  } catch (Exception\InvalidPackageKeyException $exception) {
410  $this->unregisterPackageByPackageKey($packageKey);
411  $packageStatesHasChanged = true;
412  continue;
413  } catch (Exception\InvalidPackageManifestException $exception) {
414  $this->unregisterPackageByPackageKey($packageKey);
415  $packageStatesHasChanged = true;
416  continue;
417  }
418 
419  $this->registerPackage($package);
420  }
421  if ($packageStatesHasChanged) {
422  $this->sortAndSavePackageStates();
423  }
424  }
425 
433  public function registerPackage(PackageInterface $package)
434  {
435  $packageKey = $package->getPackageKey();
436  if ($this->isPackageRegistered($packageKey)) {
437  throw new Exception\InvalidPackageStateException('Package "' . $packageKey . '" is already registered.', 1338996122);
438  }
439 
440  $this->packages[$packageKey] = $package;
441 
442  if ($package instanceof PackageInterface) {
443  foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
444  $this->packageAliasMap[strtolower($packageToReplace)] = $package->getPackageKey();
445  }
446  }
447  return $package;
448  }
449 
455  protected function unregisterPackageByPackageKey($packageKey)
456  {
457  try {
458  $package = $this->getPackage($packageKey);
459  if ($package instanceof PackageInterface) {
460  foreach ($package->getPackageReplacementKeys() as $packageToReplace => $versionConstraint) {
461  unset($this->packageAliasMap[strtolower($packageToReplace)]);
462  }
463  }
464  } catch (Exception\UnknownPackageException $e) {
465  }
466  unset($this->packages[$packageKey]);
467  unset($this->packageStatesConfiguration['packages'][$packageKey]);
468  }
469 
476  public function getPackageKeyFromComposerName($composerName)
477  {
478  $lowercasedComposerName = strtolower($composerName);
479  if (isset($this->packageAliasMap[$lowercasedComposerName])) {
480  return $this->packageAliasMap[$lowercasedComposerName];
481  }
482  if (isset($this->composerNameToPackageKeyMap[$lowercasedComposerName])) {
483  return $this->composerNameToPackageKeyMap[$lowercasedComposerName];
484  }
485  return $composerName;
486  }
487 
497  public function getPackage($packageKey)
498  {
499  if (!$this->isPackageRegistered($packageKey) && !$this->isPackageAvailable($packageKey)) {
500  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);
501  }
502  return $this->packages[$packageKey];
503  }
504 
513  public function isPackageAvailable($packageKey)
514  {
515  if ($this->isPackageRegistered($packageKey)) {
516  return true;
517  }
518 
519  // If activePackages is empty, the PackageManager is currently initializing
520  // thus packages should not be scanned
521  if (!$this->availablePackagesScanned && !empty($this->activePackages)) {
522  $this->scanAvailablePackages();
523  }
524 
525  return $this->isPackageRegistered($packageKey);
526  }
527 
535  public function isPackageActive($packageKey)
536  {
537  $packageKey = $this->getPackageKeyFromComposerName($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 
630  public function deletePackage($packageKey)
631  {
632  if (!$this->isPackageAvailable($packageKey)) {
633  throw new Exception\UnknownPackageException('Package "' . $packageKey . '" is not available and cannot be removed.', 1166543253);
634  }
635 
636  $package = $this->getPackage($packageKey);
637  if ($package->isProtected()) {
638  throw new Exception\ProtectedPackageKeyException('The package "' . $packageKey . '" is protected and cannot be removed.', 1220722120);
639  }
640 
641  if ($this->isPackageActive($packageKey)) {
642  $this->deactivatePackage($packageKey);
643  }
644 
645  $this->unregisterPackage($package);
646  $this->sortAndSavePackageStates();
647 
648  $packagePath = $package->getPackagePath();
649  $deletion = GeneralUtility::rmdir($packagePath, true);
650  if ($deletion === false) {
651  throw new Exception('Please check file permissions. The directory "' . $packagePath . '" for package "' . $packageKey . '" could not be removed.', 1301491089);
652  }
653  }
654 
663  public function getActivePackages()
664  {
665  if (empty($this->activePackages)) {
666  if (!empty($this->packageStatesConfiguration['packages'])) {
667  foreach ($this->packageStatesConfiguration['packages'] as $packageKey => $packageConfig) {
668  $this->activePackages[$packageKey] = $this->getPackage($packageKey);
669  }
670  }
671  }
672  return array_merge($this->activePackages, $this->runtimeActivatedPackages);
673  }
674 
681  protected function isPackageRegistered($packageKey)
682  {
683  $packageKey = $this->getPackageKeyFromComposerName($packageKey);
684 
685  return isset($this->packages[$packageKey]);
686  }
687 
695  protected function sortActivePackagesByDependencies()
696  {
697  $packagesWithDependencies = $this->resolvePackageDependencies($this->packageStatesConfiguration['packages']);
698 
699  // sort the packages by key at first, so we get a stable sorting of "equivalent" packages afterwards
700  ksort($packagesWithDependencies);
701  $sortedPackageKeys = $this->dependencyResolver->sortPackageStatesConfigurationByDependency($packagesWithDependencies);
702 
703  // Reorder the packages according to the loading order
704  $this->packageStatesConfiguration['packages'] = [];
705  foreach ($sortedPackageKeys as $packageKey) {
706  $this->registerActivePackage($this->packages[$packageKey]);
707  }
708  return $packagesWithDependencies;
709  }
710 
719  protected function resolvePackageDependencies($packageConfig)
720  {
721  $packagesWithDependencies = [];
722  foreach ($packageConfig as $packageKey => $_) {
723  $packagesWithDependencies[$packageKey]['dependencies'] = $this->getDependencyArrayForPackage($packageKey);
724  $packagesWithDependencies[$packageKey]['suggestions'] = $this->getSuggestionArrayForPackage($packageKey);
725  }
726  return $packagesWithDependencies;
727  }
728 
735  protected function getSuggestionArrayForPackage($packageKey)
736  {
737  if (!isset($this->packages[$packageKey])) {
738  return null;
739  }
740  $suggestedPackageKeys = [];
741  $suggestedPackageConstraints = $this->packages[$packageKey]->getPackageMetaData()->getConstraintsByType(MetaData::CONSTRAINT_TYPE_SUGGESTS);
742  foreach ($suggestedPackageConstraints as $constraint) {
743  if ($constraint instanceof MetaData\PackageConstraint) {
744  $suggestedPackageKey = $constraint->getValue();
745  if (isset($this->packages[$suggestedPackageKey])) {
746  $suggestedPackageKeys[] = $suggestedPackageKey;
747  }
748  }
749  }
750  return array_reverse($suggestedPackageKeys);
751  }
752 
759  protected function sortAndSavePackageStates()
760  {
762 
763  $this->packageStatesConfiguration['version'] = 5;
764 
765  $fileDescription = "# PackageStates.php\n\n";
766  $fileDescription .= "# This file is maintained by TYPO3's package management. Although you can edit it\n";
767  $fileDescription .= "# manually, you should rather use the extension manager for maintaining packages.\n";
768  $fileDescription .= "# This file will be regenerated automatically if it doesn't exist. Deleting this file\n";
769  $fileDescription .= "# should, however, never become necessary if you use the package commands.\n";
770 
771  if (!@is_writable($this->packageStatesPathAndFilename)) {
772  // If file does not exists try to create it
773  $fileHandle = @fopen($this->packageStatesPathAndFilename, 'x');
774  if (!$fileHandle) {
775  throw new Exception\PackageStatesFileNotWritableException(
776  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),
777  1382449759
778  );
779  }
780  fclose($fileHandle);
781  }
782  $packageStatesCode = "<?php\n$fileDescription\nreturn " . ArrayUtility::arrayExport($this->packageStatesConfiguration) . ";\n";
783  GeneralUtility::writeFile($this->packageStatesPathAndFilename, $packageStatesCode, true);
784 
786 
787  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($this->packageStatesPathAndFilename);
788  }
789 
797  public function isPackageKeyValid($packageKey)
798  {
799  return preg_match(PackageInterface::PATTERN_MATCH_PACKAGEKEY, $packageKey) === 1 || preg_match(PackageInterface::PATTERN_MATCH_EXTENSIONKEY, $packageKey) === 1;
800  }
801 
809  public function getAvailablePackages()
810  {
811  if ($this->availablePackagesScanned === false) {
812  $this->scanAvailablePackages();
813  }
814 
815  return $this->packages;
816  }
817 
824  public function unregisterPackage(PackageInterface $package)
825  {
826  $packageKey = $package->getPackageKey();
827  if (!$this->isPackageRegistered($packageKey)) {
828  throw new Exception\InvalidPackageStateException('Package "' . $packageKey . '" is not registered.', 1338996142);
829  }
830  $this->unregisterPackageByPackageKey($packageKey);
831  }
832 
839  public function reloadPackageInformation($packageKey)
840  {
841  if (!$this->isPackageRegistered($packageKey)) {
842  throw new Exception\InvalidPackageStateException('Package "' . $packageKey . '" is not registered.', 1436201329);
843  }
844 
846  $package = $this->packages[$packageKey];
847  $packagePath = $package->getPackagePath();
848  $newPackage = new Package($this, $packageKey, $packagePath);
849  $this->packages[$packageKey] = $newPackage;
850  unset($package);
851  }
852 
860  public function getComposerManifest($manifestPath)
861  {
862  $composerManifest = null;
863  if (file_exists($manifestPath . 'composer.json')) {
864  $json = file_get_contents($manifestPath . 'composer.json');
865  $composerManifest = json_decode($json);
866  if (!$composerManifest instanceof \stdClass) {
867  throw new Exception\InvalidPackageManifestException('The composer.json found for extension "' . basename($manifestPath) . '" is invalid!', 1439555561);
868  }
869  }
870 
871  $extensionManagerConfiguration = $this->getExtensionEmConf($manifestPath);
872  $composerManifest = $this->mapExtensionManagerConfigurationToComposerManifest(
873  basename($manifestPath),
874  $extensionManagerConfiguration,
875  $composerManifest ?: new \stdClass()
876  );
877 
878  return $composerManifest;
879  }
880 
889  protected function getExtensionEmConf($packagePath)
890  {
891  $packageKey = basename($packagePath);
892  $_EXTKEY = $packageKey;
893  $path = $packagePath . 'ext_emconf.php';
894  $EM_CONF = null;
895  if (@file_exists($path)) {
896  include $path;
897  if (is_array($EM_CONF[$_EXTKEY])) {
898  return $EM_CONF[$_EXTKEY];
899  }
900  }
901  throw new Exception\InvalidPackageManifestException('No valid ext_emconf.php file found for package "' . $packageKey . '".', 1360403545);
902  }
903 
913  protected function mapExtensionManagerConfigurationToComposerManifest($packageKey, array $extensionManagerConfiguration, \stdClass $composerManifest)
914  {
915  $this->setComposerManifestValueIfEmpty($composerManifest, 'name', $packageKey);
916  $this->setComposerManifestValueIfEmpty($composerManifest, 'type', 'typo3-cms-extension');
917  $this->setComposerManifestValueIfEmpty($composerManifest, 'description', $extensionManagerConfiguration['title']);
918  $this->setComposerManifestValueIfEmpty($composerManifest, 'authors', [['name' => $extensionManagerConfiguration['author'], 'email' => $extensionManagerConfiguration['author_email']]]);
919  $composerManifest->version = $extensionManagerConfiguration['version'];
920  if (isset($extensionManagerConfiguration['constraints']['depends']) && is_array($extensionManagerConfiguration['constraints']['depends'])) {
921  $composerManifest->require = new \stdClass();
922  foreach ($extensionManagerConfiguration['constraints']['depends'] as $requiredPackageKey => $requiredPackageVersion) {
923  if (!empty($requiredPackageKey)) {
924  if ($requiredPackageKey === 'typo3') {
925  // Add implicit dependency to 'core'
926  $composerManifest->require->core = $requiredPackageVersion;
927  } elseif ($requiredPackageKey !== 'php') {
928  // Skip php dependency
929  $composerManifest->require->{$requiredPackageKey} = $requiredPackageVersion;
930  }
931  } else {
932  throw new Exception\InvalidPackageManifestException(sprintf('The extension "%s" has invalid version constraints in depends section. Extension key is missing!', $packageKey), 1439552058);
933  }
934  }
935  }
936  if (isset($extensionManagerConfiguration['constraints']['conflicts']) && is_array($extensionManagerConfiguration['constraints']['conflicts'])) {
937  $composerManifest->conflict = new \stdClass();
938  foreach ($extensionManagerConfiguration['constraints']['conflicts'] as $conflictingPackageKey => $conflictingPackageVersion) {
939  if (!empty($conflictingPackageKey)) {
940  $composerManifest->conflict->$conflictingPackageKey = $conflictingPackageVersion;
941  } else {
942  throw new Exception\InvalidPackageManifestException(sprintf('The extension "%s" has invalid version constraints in conflicts section. Extension key is missing!', $packageKey), 1439552059);
943  }
944  }
945  }
946  if (isset($extensionManagerConfiguration['constraints']['suggests']) && is_array($extensionManagerConfiguration['constraints']['suggests'])) {
947  $composerManifest->suggest = new \stdClass();
948  foreach ($extensionManagerConfiguration['constraints']['suggests'] as $suggestedPackageKey => $suggestedPackageVersion) {
949  if (!empty($suggestedPackageKey)) {
950  $composerManifest->suggest->$suggestedPackageKey = $suggestedPackageVersion;
951  } else {
952  throw new Exception\InvalidPackageManifestException(sprintf('The extension "%s" has invalid version constraints in suggests section. Extension key is missing!', $packageKey), 1439552060);
953  }
954  }
955  }
956  if (isset($extensionManagerConfiguration['autoload'])) {
957  $composerManifest->autoload = json_decode(json_encode($extensionManagerConfiguration['autoload']));
958  }
959  // composer.json autoload-dev information must be discarded, as it may contain information only available after a composer install
960  unset($composerManifest->{'autoload-dev'});
961  if (isset($extensionManagerConfiguration['autoload-dev'])) {
962  $composerManifest->{'autoload-dev'} = json_decode(json_encode($extensionManagerConfiguration['autoload-dev']));
963  }
964 
965  return $composerManifest;
966  }
967 
974  protected function setComposerManifestValueIfEmpty(\stdClass $manifest, $property, $value)
975  {
976  if (empty($manifest->{$property})) {
977  $manifest->{$property} = $value;
978  }
979 
980  return $manifest;
981  }
982 
994  protected function getDependencyArrayForPackage($packageKey, array &$dependentPackageKeys = [], array $trace = [])
995  {
996  if (!isset($this->packages[$packageKey])) {
997  return null;
998  }
999  if (in_array($packageKey, $trace, true) !== false) {
1000  return $dependentPackageKeys;
1001  }
1002  $trace[] = $packageKey;
1003  $dependentPackageConstraints = $this->packages[$packageKey]->getPackageMetaData()->getConstraintsByType(MetaData::CONSTRAINT_TYPE_DEPENDS);
1004  foreach ($dependentPackageConstraints as $constraint) {
1005  if ($constraint instanceof MetaData\PackageConstraint) {
1006  $dependentPackageKey = $constraint->getValue();
1007  if (in_array($dependentPackageKey, $dependentPackageKeys, true) === false && in_array($dependentPackageKey, $trace, true) === false) {
1008  $dependentPackageKeys[] = $dependentPackageKey;
1009  }
1010  $this->getDependencyArrayForPackage($dependentPackageKey, $dependentPackageKeys, $trace);
1011  }
1012  }
1013  return array_reverse($dependentPackageKeys);
1014  }
1015 
1031  protected function getPackageKeyFromManifest($manifest, $packagePath)
1032  {
1033  if (!is_object($manifest)) {
1034  throw new Exception\InvalidPackageManifestException('Invalid composer manifest in package path: ' . $packagePath, 1348146451);
1035  }
1036  if (isset($manifest->type) && substr($manifest->type, 0, 10) === 'typo3-cms-') {
1037  $packageKey = basename($packagePath);
1038  return preg_replace('/[^A-Za-z0-9._-]/', '', $packageKey);
1039  }
1040  $packageKey = str_replace('/', '.', $manifest->name);
1041  return preg_replace('/[^A-Za-z0-9.]/', '', $packageKey);
1042  }
1043 
1050  protected function getPackageBasePaths()
1051  {
1052  if (count($this->packagesBasePaths) < 3) {
1053  // Check if the directory even exists and if it is not empty
1054  if (is_dir(PATH_typo3conf . 'ext') && $this->hasSubDirectories(PATH_typo3conf . 'ext')) {
1055  $this->packagesBasePaths['local'] = PATH_typo3conf . 'ext/*/';
1056  }
1057  if (is_dir(PATH_typo3 . 'ext') && $this->hasSubDirectories(PATH_typo3 . 'ext')) {
1058  $this->packagesBasePaths['global'] = PATH_typo3 . 'ext/*/';
1059  }
1060  $this->packagesBasePaths['system'] = PATH_typo3 . 'sysext/*/';
1061  }
1062  return $this->packagesBasePaths;
1063  }
1064 
1071  protected function hasSubDirectories(string $path): bool
1072  {
1073  return !empty(glob(rtrim($path, '/\\') . '/*', GLOB_ONLYDIR));
1074  }
1075 }
injectCoreCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache)
registerTransientClassLoadingInformationForPackage(PackageInterface $package)
registerPackage(PackageInterface $package)
getPackageKeyFromManifest($manifest, $packagePath)
injectDependencyResolver(DependencyResolver $dependencyResolver)
registerPackagesFromConfiguration(array $packages, $registerOnlyNewPackages=false, $packageStatesHasChanged=false)
static makeInstance($className,... $constructorArguments)
static arrayExport(array $array=[], $level=0)
setComposerManifestValueIfEmpty(\stdClass $manifest, $property, $value)
registerActivePackage(PackageInterface $package)
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)
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)
static sanitizeTrailingSeparator($path, $separator='/')