vendor/pimcore/pimcore/lib/Extension/Bundle/PimcoreBundleManager.php line 554

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4.  * Pimcore
  5.  *
  6.  * This source file is available under two different licenses:
  7.  * - GNU General Public License version 3 (GPLv3)
  8.  * - Pimcore Commercial License (PCL)
  9.  * Full copyright and license information is available in
  10.  * LICENSE.md which is distributed with this source code.
  11.  *
  12.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  13.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  14.  */
  15. namespace Pimcore\Extension\Bundle;
  16. use Pimcore\Event\BundleManager\PathsEvent;
  17. use Pimcore\Event\BundleManagerEvents;
  18. use Pimcore\Extension\Bundle\Config\StateConfig;
  19. use Pimcore\Extension\Bundle\Exception\BundleNotFoundException;
  20. use Pimcore\Extension\Bundle\Installer\Exception\InstallationException;
  21. use Pimcore\HttpKernel\BundleCollection\ItemInterface;
  22. use Pimcore\Kernel;
  23. use Pimcore\Routing\RouteReferenceInterface;
  24. use Symfony\Component\Routing\RouterInterface;
  25. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  26. /**
  27.  * @internal
  28.  */
  29. class PimcoreBundleManager
  30. {
  31.     /**
  32.      * @var StateConfig
  33.      */
  34.     protected $stateConfig;
  35.     /**
  36.      * @var PimcoreBundleLocator
  37.      */
  38.     protected $bundleLocator;
  39.     /**
  40.      * @var Kernel
  41.      */
  42.     protected $kernel;
  43.     /**
  44.      * @var EventDispatcherInterface
  45.      */
  46.     protected $dispatcher;
  47.     /**
  48.      * @var RouterInterface
  49.      */
  50.     protected $router;
  51.     /**
  52.      * @var array
  53.      */
  54.     protected $availableBundles;
  55.     /**
  56.      * @var array
  57.      */
  58.     protected $enabledBundles;
  59.     /**
  60.      * @var array
  61.      */
  62.     protected $manuallyRegisteredBundleState;
  63.     /**
  64.      * @param StateConfig $stateConfig
  65.      * @param PimcoreBundleLocator $bundleLocator
  66.      * @param Kernel $kernel
  67.      * @param EventDispatcherInterface $dispatcher
  68.      * @param RouterInterface $router
  69.      */
  70.     public function __construct(
  71.         StateConfig $stateConfig,
  72.         PimcoreBundleLocator $bundleLocator,
  73.         Kernel $kernel,
  74.         EventDispatcherInterface $dispatcher,
  75.         RouterInterface $router
  76.     ) {
  77.         $this->stateConfig $stateConfig;
  78.         $this->bundleLocator $bundleLocator;
  79.         $this->kernel $kernel;
  80.         $this->dispatcher $dispatcher;
  81.         $this->router $router;
  82.     }
  83.     /**
  84.      * List of currently active bundles from kernel. A bundle can be in this list without being enabled via extensions
  85.      * config file if it is registered manually on the kernel.
  86.      *
  87.      * @param bool $onlyInstalled
  88.      *
  89.      * @return PimcoreBundleInterface[]
  90.      */
  91.     public function getActiveBundles(bool $onlyInstalled true): array
  92.     {
  93.         $bundles = [];
  94.         foreach ($this->kernel->getBundles() as $bundle) {
  95.             if ($bundle instanceof PimcoreBundleInterface) {
  96.                 if ($onlyInstalled && !$this->isInstalled($bundle)) {
  97.                     continue;
  98.                 }
  99.                 $bundles[get_class($bundle)] = $bundle;
  100.             }
  101.         }
  102.         return $bundles;
  103.     }
  104.     /**
  105.      * @param string $id
  106.      * @param bool $onlyInstalled
  107.      *
  108.      * @return PimcoreBundleInterface
  109.      */
  110.     public function getActiveBundle(string $idbool $onlyInstalled true): PimcoreBundleInterface
  111.     {
  112.         foreach ($this->getActiveBundles($onlyInstalled) as $bundle) {
  113.             if ($this->getBundleIdentifier($bundle) === $id) {
  114.                 return $bundle;
  115.             }
  116.         }
  117.         throw new BundleNotFoundException(sprintf('Bundle %s was not found'$id));
  118.     }
  119.     /**
  120.      * List of available bundles from a defined set of paths
  121.      *
  122.      * @return array
  123.      */
  124.     public function getAvailableBundles(): array
  125.     {
  126.         if (null === $this->availableBundles) {
  127.             $bundles $this->getManuallyRegisteredBundleNames(false);
  128.             foreach ($this->bundleLocator->findBundles() as $locatedBundle) {
  129.                 if (!in_array($locatedBundle$bundles)) {
  130.                     $bundles[] = $locatedBundle;
  131.                 }
  132.             }
  133.             sort($bundles);
  134.             $this->availableBundles $bundles;
  135.         }
  136.         return $this->availableBundles;
  137.     }
  138.     /**
  139.      * Lists enabled bundle names
  140.      *
  141.      * @return array
  142.      */
  143.     public function getEnabledBundleNames(): array
  144.     {
  145.         $bundleNames array_merge(
  146.             $this->getManuallyRegisteredBundleNames(true),
  147.             $this->stateConfig->getEnabledBundleNames()
  148.         );
  149.         $bundleNames array_unique($bundleNames);
  150.         sort($bundleNames);
  151.         return $bundleNames;
  152.     }
  153.     /**
  154.      * Returns names of manually registered bundles (not registered via extension manager)
  155.      *
  156.      * @param bool $onlyEnabled
  157.      *
  158.      * @return array
  159.      */
  160.     private function getManuallyRegisteredBundleNames(bool $onlyEnabled false): array
  161.     {
  162.         $state $this->getManuallyRegisteredBundleState();
  163.         if (!$onlyEnabled) {
  164.             return array_keys($state);
  165.         }
  166.         $bundleNames = [];
  167.         foreach ($state as $bundleName => $options) {
  168.             if ($options['enabled']) {
  169.                 $bundleNames[] = $bundleName;
  170.             }
  171.         }
  172.         return $bundleNames;
  173.     }
  174.     /**
  175.      * Builds state infos about manually configured bundles (not registered via extension manager)
  176.      *
  177.      * @return array
  178.      */
  179.     private function getManuallyRegisteredBundleState()
  180.     {
  181.         if (null === $this->manuallyRegisteredBundleState) {
  182.             $collection $this->kernel->getBundleCollection();
  183.             $enabledBundles array_keys($this->getActiveBundles(false));
  184.             $bundles = [];
  185.             foreach ($collection->getItems() as $item) {
  186.                 if (!$item->isPimcoreBundle()) {
  187.                     continue;
  188.                 }
  189.                 if ($item->getSource() === ItemInterface::SOURCE_EXTENSION_MANAGER_CONFIG) {
  190.                     continue;
  191.                 }
  192.                 $bundles[$item->getBundleIdentifier()] = $this->stateConfig->normalizeOptions([
  193.                     'enabled' => in_array($item->getBundleIdentifier(), $enabledBundles),
  194.                     'priority' => $item->getPriority(),
  195.                     'environments' => $item->getEnvironments(),
  196.                 ]);
  197.             }
  198.             $this->manuallyRegisteredBundleState $bundles;
  199.         }
  200.         return $this->manuallyRegisteredBundleState;
  201.     }
  202.     /**
  203.      * Determines if a bundle exists (is enabled or can be enabled)
  204.      *
  205.      * @param string|PimcoreBundleInterface $bundle
  206.      *
  207.      * @return bool
  208.      */
  209.     public function exists($bundle): bool
  210.     {
  211.         $identifier $this->getBundleIdentifier($bundle);
  212.         return $this->isValidBundleIdentifier($identifier);
  213.     }
  214.     /**
  215.      * @param string|PimcoreBundleInterface $bundle
  216.      *
  217.      * @return string
  218.      */
  219.     public function getBundleIdentifier($bundle): string
  220.     {
  221.         $identifier $bundle;
  222.         if ($bundle instanceof PimcoreBundleInterface) {
  223.             $identifier get_class($bundle);
  224.         }
  225.         return $identifier;
  226.     }
  227.     /**
  228.      * @param string $identifier
  229.      *
  230.      * @return bool
  231.      */
  232.     protected function isValidBundleIdentifier(string $identifier): bool
  233.     {
  234.         $validNames array_merge(
  235.             array_keys($this->getActiveBundles(false)),
  236.             $this->getAvailableBundles()
  237.         );
  238.         return in_array($identifier$validNames);
  239.     }
  240.     /**
  241.      * Validates bundle name against list if available and active bundles
  242.      *
  243.      * @param string $identifier
  244.      */
  245.     protected function validateBundleIdentifier(string $identifier)
  246.     {
  247.         if (!$this->isValidBundleIdentifier($identifier)) {
  248.             throw new BundleNotFoundException(sprintf('Bundle "%s" is no valid bundle identifier'$identifier));
  249.         }
  250.     }
  251.     /**
  252.      * Determines if the bundle was programatically registered (not via extension manager)
  253.      *
  254.      * @param string|PimcoreBundleInterface $bundle
  255.      *
  256.      * @return bool
  257.      */
  258.     public function isManuallyRegistered($bundle): bool
  259.     {
  260.         $identifier $this->getBundleIdentifier($bundle);
  261.         $this->validateBundleIdentifier($identifier);
  262.         return in_array($identifier$this->getManuallyRegisteredBundleNames(false));
  263.     }
  264.     /**
  265.      * Checks if a state change (enable/disable, priority, environments) is possible
  266.      *
  267.      * @param string $identifier
  268.      */
  269.     protected function validateStateChange(string $identifier)
  270.     {
  271.         if ($this->isManuallyRegistered($identifier)) {
  272.             throw new \LogicException(sprintf(
  273.                 'Can\'t change state for bundle "%s" as it is programatically registered',
  274.                 $identifier
  275.             ));
  276.         }
  277.     }
  278.     /**
  279.      * Determines if bundle is allowed to change state (can be enabled/disabled)
  280.      *
  281.      * @param string|PimcoreBundleInterface $bundle
  282.      *
  283.      * @return bool
  284.      */
  285.     public function canChangeState($bundle): bool
  286.     {
  287.         $identifier $this->getBundleIdentifier($bundle);
  288.         $this->validateBundleIdentifier($identifier);
  289.         return !$this->isManuallyRegistered($bundle);
  290.     }
  291.     /**
  292.      * Reads bundle state from config
  293.      *
  294.      * @param string|PimcoreBundleInterface $bundle
  295.      *
  296.      * @return array
  297.      */
  298.     public function getState($bundle): array
  299.     {
  300.         $identifier $this->getBundleIdentifier($bundle);
  301.         $this->validateBundleIdentifier($identifier);
  302.         if ($this->isManuallyRegistered($identifier)) {
  303.             return $this->getManuallyRegisteredBundleState()[$identifier];
  304.         }
  305.         return $this->stateConfig->getState($identifier);
  306.     }
  307.     /**
  308.      * Updates state for a bundle and writes it to config
  309.      *
  310.      * @param string|PimcoreBundleInterface $bundle
  311.      * @param array $options
  312.      */
  313.     public function setState($bundle, array $options)
  314.     {
  315.         $identifier $this->getBundleIdentifier($bundle);
  316.         $this->validateBundleIdentifier($identifier);
  317.         $this->validateStateChange($identifier);
  318.         $this->stateConfig->setState($identifier$options);
  319.     }
  320.     /**
  321.      * Batch updates bundle states
  322.      *
  323.      * @param array $states
  324.      */
  325.     public function setStates(array $states)
  326.     {
  327.         $updates = [];
  328.         foreach ($states as $bundle => $options) {
  329.             $identifier $this->getBundleIdentifier($bundle);
  330.             $this->validateBundleIdentifier($identifier);
  331.             $this->validateStateChange($identifier);
  332.             $updates[$identifier] = $options;
  333.         }
  334.         $this->stateConfig->setStates($updates);
  335.     }
  336.     /**
  337.      * Enables a bundle
  338.      *
  339.      * @param string|PimcoreBundleInterface $bundle
  340.      * @param array $state Optional additional state config (see StateConfig)
  341.      */
  342.     public function enable($bundle, array $state = [])
  343.     {
  344.         $state array_merge($state, [
  345.             'enabled' => true,
  346.         ]);
  347.         $this->setState($bundle$state);
  348.     }
  349.     /**
  350.      * Disables a bundle
  351.      *
  352.      * @param string|PimcoreBundleInterface $bundle
  353.      */
  354.     public function disable($bundle)
  355.     {
  356.         $this->setState($bundle, ['enabled' => false]);
  357.     }
  358.     /**
  359.      * Determines if a bundle is enabled
  360.      *
  361.      * @param string|PimcoreBundleInterface $bundle
  362.      *
  363.      * @return bool
  364.      */
  365.     public function isEnabled($bundle): bool
  366.     {
  367.         $identifier $this->getBundleIdentifier($bundle);
  368.         $this->validateBundleIdentifier($identifier);
  369.         return in_array($identifier$this->getEnabledBundleNames());
  370.     }
  371.     /**
  372.      * @param PimcoreBundleInterface $bundle
  373.      * @param bool $throwException
  374.      *
  375.      * @return null|Installer\InstallerInterface
  376.      */
  377.     protected function loadBundleInstaller(PimcoreBundleInterface $bundlebool $throwException false)
  378.     {
  379.         if (null === $installer $bundle->getInstaller()) {
  380.             if ($throwException) {
  381.                 throw new InstallationException(sprintf('Bundle %s does not a define an installer'$bundle->getName()));
  382.             }
  383.             return null;
  384.         }
  385.         return $installer;
  386.     }
  387.     /**
  388.      * Returns the bundle installer if configured
  389.      *
  390.      * @param PimcoreBundleInterface $bundle
  391.      * @param bool $throwException
  392.      *
  393.      * @return null|Installer\InstallerInterface
  394.      */
  395.     public function getInstaller(PimcoreBundleInterface $bundlebool $throwException false)
  396.     {
  397.         return $this->loadBundleInstaller($bundle$throwException);
  398.     }
  399.     /**
  400.      * Runs install routine for a bundle
  401.      *
  402.      * @param PimcoreBundleInterface $bundle
  403.      *
  404.      * @throws InstallationException If the bundle can not be installed or doesn't define an installer
  405.      */
  406.     public function install(PimcoreBundleInterface $bundle)
  407.     {
  408.         $installer $this->loadBundleInstaller($bundletrue);
  409.         if (!$this->canBeInstalled($bundle)) {
  410.             throw new InstallationException(sprintf('Bundle %s can not be installed'$bundle->getName()));
  411.         }
  412.         $installer->install();
  413.     }
  414.     /**
  415.      * Runs uninstall routine for a bundle
  416.      *
  417.      * @param PimcoreBundleInterface $bundle
  418.      *
  419.      * @throws InstallationException If the bundle can not be uninstalled or doesn't define an installer
  420.      */
  421.     public function uninstall(PimcoreBundleInterface $bundle)
  422.     {
  423.         $installer $this->loadBundleInstaller($bundletrue);
  424.         if (!$this->canBeUninstalled($bundle)) {
  425.             throw new InstallationException(sprintf('Bundle %s can not be uninstalled'$bundle->getName()));
  426.         }
  427.         $installer->uninstall();
  428.     }
  429.     /**
  430.      * Determines if a bundle can be installed
  431.      *
  432.      * @param PimcoreBundleInterface $bundle
  433.      *
  434.      * @return bool
  435.      */
  436.     public function canBeInstalled(PimcoreBundleInterface $bundle): bool
  437.     {
  438.         if (!$this->isEnabled($bundle)) {
  439.             return false;
  440.         }
  441.         if (null === $installer $this->loadBundleInstaller($bundle)) {
  442.             return false;
  443.         }
  444.         return $installer->canBeInstalled();
  445.     }
  446.     /**
  447.      * Determines if a bundle can be uninstalled
  448.      *
  449.      * @param PimcoreBundleInterface $bundle
  450.      *
  451.      * @return bool
  452.      */
  453.     public function canBeUninstalled(PimcoreBundleInterface $bundle): bool
  454.     {
  455.         if (!$this->isEnabled($bundle)) {
  456.             return false;
  457.         }
  458.         if (null === $installer $this->loadBundleInstaller($bundle)) {
  459.             return false;
  460.         }
  461.         return $installer->canBeUninstalled();
  462.     }
  463.     /**
  464.      * Determines if a bundle is installed
  465.      *
  466.      * @param PimcoreBundleInterface $bundle
  467.      *
  468.      * @return bool
  469.      */
  470.     public function isInstalled(PimcoreBundleInterface $bundle): bool
  471.     {
  472.         if (null === $installer $bundle->getInstaller()) {
  473.             // bundle has no dedicated installer, so we can treat it as installed
  474.             return true;
  475.         }
  476.         return $installer->isInstalled();
  477.     }
  478.     /**
  479.      * Determines if a reload is needed after installation
  480.      *
  481.      * @param PimcoreBundleInterface $bundle
  482.      *
  483.      * @return bool
  484.      */
  485.     public function needsReloadAfterInstall(PimcoreBundleInterface $bundle): bool
  486.     {
  487.         if (null === $installer $bundle->getInstaller()) {
  488.             // bundle has no dedicated installer
  489.             return false;
  490.         }
  491.         return $installer->needsReloadAfterInstall();
  492.     }
  493.     /**
  494.      * Resolves all admin javascripts to load
  495.      *
  496.      * @return array
  497.      */
  498.     public function getJsPaths(): array
  499.     {
  500.         $paths $this->resolvePaths('js');
  501.         return $this->resolveEventPaths($pathsBundleManagerEvents::JS_PATHS);
  502.     }
  503.     /**
  504.      * Resolves all admin stylesheets to load
  505.      *
  506.      * @return array
  507.      */
  508.     public function getCssPaths(): array
  509.     {
  510.         $paths $this->resolvePaths('css');
  511.         return $this->resolveEventPaths($pathsBundleManagerEvents::CSS_PATHS);
  512.     }
  513.     /**
  514.      * Resolves all editmode javascripts to load
  515.      *
  516.      * @return array
  517.      */
  518.     public function getEditmodeJsPaths(): array
  519.     {
  520.         $paths $this->resolvePaths('js''editmode');
  521.         return $this->resolveEventPaths($pathsBundleManagerEvents::EDITMODE_JS_PATHS);
  522.     }
  523.     /**
  524.      * Resolves all editmode stylesheets to load
  525.      *
  526.      * @return array
  527.      */
  528.     public function getEditmodeCssPaths(): array
  529.     {
  530.         $paths $this->resolvePaths('css''editmode');
  531.         return $this->resolveEventPaths($pathsBundleManagerEvents::EDITMODE_CSS_PATHS);
  532.     }
  533.     /**
  534.      * Iterates installed bundles and fetches asset paths
  535.      *
  536.      * @param string $type
  537.      * @param string|null $mode
  538.      *
  539.      * @return array
  540.      */
  541.     protected function resolvePaths(string $typestring $mode null): array
  542.     {
  543.         $type ucfirst($type);
  544.         if (null !== $mode) {
  545.             $mode ucfirst($mode);
  546.         } else {
  547.             $mode '';
  548.         }
  549.         // getJsPaths, getEditmodeJsPaths
  550.         $getter sprintf('get%s%sPaths'$mode$type);
  551.         $result = [];
  552.         foreach ($this->getActiveBundles() as $bundle) {
  553.             $paths $bundle->$getter();
  554.             foreach ($paths as $path) {
  555.                 if ($path instanceof RouteReferenceInterface) {
  556.                     $result[] = $this->router->generate(
  557.                         $path->getRoute(),
  558.                         $path->getParameters(),
  559.                         $path->getType()
  560.                     );
  561.                 } else {
  562.                     $result[] = $path;
  563.                 }
  564.             }
  565.         }
  566.         return $result;
  567.     }
  568.     /**
  569.      * Emits given path event
  570.      *
  571.      * @param array  $paths
  572.      * @param string $eventName
  573.      *
  574.      * @return array
  575.      */
  576.     protected function resolveEventPaths(array $pathsstring $eventName): array
  577.     {
  578.         $event = new PathsEvent($paths);
  579.         $this->dispatcher->dispatch($event$eventName);
  580.         return $event->getPaths();
  581.     }
  582. }