vendor/thelia/core/lib/Thelia/Action/Module.php line 424

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Thelia package.
  4.  * http://www.thelia.net
  5.  *
  6.  * (c) OpenStudio <info@thelia.net>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Thelia\Action;
  12. use Exception;
  13. use Propel\Runtime\Propel;
  14. use SplFileInfo;
  15. use Symfony\Component\DependencyInjection\ContainerInterface;
  16. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  17. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  18. use Symfony\Component\Filesystem\Exception\IOException;
  19. use Symfony\Component\Filesystem\Filesystem;
  20. use Symfony\Component\HttpFoundation\Response;
  21. use Thelia\Core\Event\Cache\CacheEvent;
  22. use Thelia\Core\Event\Module\ModuleDeleteEvent;
  23. use Thelia\Core\Event\Module\ModuleEvent;
  24. use Thelia\Core\Event\Module\ModuleInstallEvent;
  25. use Thelia\Core\Event\Module\ModuleToggleActivationEvent;
  26. use Thelia\Core\Event\Order\OrderPaymentEvent;
  27. use Thelia\Core\Event\TheliaEvents;
  28. use Thelia\Core\Event\UpdatePositionEvent;
  29. use Thelia\Core\Translation\Translator;
  30. use Thelia\Exception\FileNotFoundException;
  31. use Thelia\Exception\ModuleException;
  32. use Thelia\Log\Tlog;
  33. use Thelia\Model\Base\OrderQuery;
  34. use Thelia\Model\Map\ModuleTableMap;
  35. use Thelia\Model\ModuleQuery;
  36. use Thelia\Module\BaseModule;
  37. use Thelia\Module\ModuleManagement;
  38. use Thelia\Module\Validator\ModuleValidator;
  39. /**
  40.  * Class Module.
  41.  *
  42.  * @author  Manuel Raynaud <manu@raynaud.io>
  43.  */
  44. class Module extends BaseAction implements EventSubscriberInterface
  45. {
  46.     /** @var ContainerInterface */
  47.     protected $container;
  48.     public function __construct(ContainerInterface $container)
  49.     {
  50.         $this->container $container;
  51.     }
  52.     public function toggleActivation(ModuleToggleActivationEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  53.     {
  54.         if (null !== $module ModuleQuery::create()->findPk($event->getModuleId())) {
  55.             $moduleInstance $module->createInstance();
  56.             if (method_exists($moduleInstance'setContainer')) {
  57.                 $moduleInstance->setContainer($this->container);
  58.                 if ($module->getActivate() == BaseModule::IS_ACTIVATED) {
  59.                     $moduleInstance->deActivate($module);
  60.                 } else {
  61.                     $moduleInstance->activate($module);
  62.                 }
  63.             }
  64.             $event->setModule($module);
  65.             $this->cacheClear($dispatcher);
  66.         }
  67.     }
  68.     public function checkToggleActivation(ModuleToggleActivationEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  69.     {
  70.         if (true === $event->isNoCheck()) {
  71.             return;
  72.         }
  73.         if (null !== $module ModuleQuery::create()->findPk($event->getModuleId())) {
  74.             try {
  75.                 if ($module->getActivate() == BaseModule::IS_ACTIVATED) {
  76.                     if ($module->getMandatory() == BaseModule::IS_MANDATORY && $event->getAssumeDeactivate() === false) {
  77.                         throw new \Exception(
  78.                             Translator::getInstance()->trans('Can\'t deactivate a secure module')
  79.                         );
  80.                     }
  81.                     if ($event->isRecursive()) {
  82.                         $this->recursiveDeactivation($event$eventName$dispatcher);
  83.                     }
  84.                     $this->checkDeactivation($module);
  85.                 } else {
  86.                     if ($event->isRecursive()) {
  87.                         $this->recursiveActivation($event$eventName$dispatcher);
  88.                     }
  89.                     $this->checkActivation($module);
  90.                 }
  91.             } catch (\Exception $ex) {
  92.                 $event->stopPropagation();
  93.                 throw $ex;
  94.             }
  95.         }
  96.     }
  97.     /**
  98.      * Check if module can be activated : supported version of Thelia, module dependencies.
  99.      *
  100.      * @param \Thelia\Model\Module $module
  101.      *
  102.      * @throws Exception if activation fails
  103.      *
  104.      * @return bool true if the module can be activated, otherwise false
  105.      */
  106.     private function checkActivation($module)
  107.     {
  108.         try {
  109.             $moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir());
  110.             $moduleValidator->validate(false);
  111.         } catch (\Exception $ex) {
  112.             throw $ex;
  113.         }
  114.         return true;
  115.     }
  116.     /**
  117.      * Check if module can be deactivated safely because other modules
  118.      * could have dependencies to this module.
  119.      *
  120.      * @param \Thelia\Model\Module $module
  121.      *
  122.      * @return bool true if the module can be deactivated, otherwise false
  123.      */
  124.     private function checkDeactivation($module)
  125.     {
  126.         $moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir());
  127.         $modules $moduleValidator->getModulesDependOf();
  128.         if (\count($modules) > 0) {
  129.             $moduleList implode(', 'array_column($modules'code'));
  130.             $message = (\count($modules) == 1)
  131.                 ? Translator::getInstance()->trans(
  132.                     '%s has dependency to module %s. You have to deactivate this module before.'
  133.                 )
  134.                 : Translator::getInstance()->trans(
  135.                     '%s have dependencies to module %s. You have to deactivate these modules before.'
  136.                 );
  137.             throw new ModuleException(
  138.                 sprintf($message$moduleList$moduleValidator->getModuleDefinition()->getCode())
  139.             );
  140.         }
  141.         return true;
  142.     }
  143.     /**
  144.      * Get dependencies of the current module and activate it if needed.
  145.      *
  146.      * @param $eventName
  147.      */
  148.     public function recursiveActivation(ModuleToggleActivationEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  149.     {
  150.         if (null !== $module ModuleQuery::create()->findPk($event->getModuleId())) {
  151.             $moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir());
  152.             $dependencies $moduleValidator->getCurrentModuleDependencies();
  153.             foreach ($dependencies as $defMod) {
  154.                 $submodule ModuleQuery::create()
  155.                     ->findOneByCode($defMod['code']);
  156.                 if ($submodule && $submodule->getActivate() != BaseModule::IS_ACTIVATED) {
  157.                     $subevent = new ModuleToggleActivationEvent($submodule->getId());
  158.                     $subevent->setRecursive(true);
  159.                     $dispatcher->dispatch($subeventTheliaEvents::MODULE_TOGGLE_ACTIVATION);
  160.                 }
  161.             }
  162.         }
  163.     }
  164.     /**
  165.      * Get modules having current module in dependence and deactivate it if needed.
  166.      *
  167.      * @param $eventName
  168.      */
  169.     public function recursiveDeactivation(ModuleToggleActivationEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  170.     {
  171.         if (null !== $module ModuleQuery::create()->findPk($event->getModuleId())) {
  172.             $moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir());
  173.             $dependencies $moduleValidator->getModulesDependOf(true);
  174.             foreach ($dependencies as $defMod) {
  175.                 $submodule ModuleQuery::create()
  176.                     ->findOneByCode($defMod['code']);
  177.                 if ($submodule && $submodule->getActivate() == BaseModule::IS_ACTIVATED) {
  178.                     $subevent = new ModuleToggleActivationEvent($submodule->getId());
  179.                     $subevent->setRecursive(true);
  180.                     $dispatcher->dispatch($subeventTheliaEvents::MODULE_TOGGLE_ACTIVATION);
  181.                 }
  182.             }
  183.         }
  184.     }
  185.     public function delete(ModuleDeleteEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  186.     {
  187.         $con Propel::getWriteConnection(ModuleTableMap::DATABASE_NAME);
  188.         $con->beginTransaction();
  189.         if (null !== $module ModuleQuery::create()->findPk($event->getModuleId(), $con)) {
  190.             try {
  191.                 if (null === $module->getFullNamespace()) {
  192.                     throw new \LogicException(
  193.                         Translator::getInstance()->trans(
  194.                             'Cannot instantiate module "%name%": the namespace is null. Maybe the model is not loaded ?',
  195.                             ['%name%' => $module->getCode()]
  196.                         )
  197.                     );
  198.                 }
  199.                 // If the module is referenced by an order, display a meaningful error
  200.                 // instead of 'delete cannot delete' caused by a constraint violation.
  201.                 // FIXME: we hav to find a way to delete modules used by order.
  202.                 if (OrderQuery::create()->filterByDeliveryModuleId($module->getId())->count() > 0
  203.                     ||
  204.                     OrderQuery::create()->filterByPaymentModuleId($module->getId())->count() > 0
  205.                 ) {
  206.                     throw new \LogicException(
  207.                         Translator::getInstance()->trans(
  208.                             'The module "%name%" is currently in use by at least one order, and can\'t be deleted.',
  209.                             ['%name%' => $module->getCode()]
  210.                         )
  211.                     );
  212.                 }
  213.                 try {
  214.                     if ($module->getMandatory() == BaseModule::IS_MANDATORY && $event->getAssumeDelete() === false) {
  215.                         throw new \Exception(
  216.                             Translator::getInstance()->trans('Can\'t remove a core module')
  217.                         );
  218.                     }
  219.                     // First, try to create an instance
  220.                     $instance $module->createInstance();
  221.                     // Then, if module is activated, check if we can deactivate it
  222.                     if ($module->getActivate()) {
  223.                         // check for modules that depend of this one
  224.                         $this->checkDeactivation($module);
  225.                     }
  226.                     $instance->setContainer($this->container);
  227.                     $path $module->getAbsoluteBaseDir();
  228.                     $instance->destroy($con$event->getDeleteData());
  229.                     $fs = new Filesystem();
  230.                     $fs->remove($path);
  231.                 } catch (\ReflectionException $ex) {
  232.                     // Happens probably because the module directory has been deleted.
  233.                     // Log a warning, and delete the database entry.
  234.                     Tlog::getInstance()->addWarning(
  235.                         Translator::getInstance()->trans(
  236.                             'Failed to create instance of module "%name%" when trying to delete module. Module directory has probably been deleted',
  237.                             ['%name%' => $module->getCode()]
  238.                         )
  239.                     );
  240.                 } catch (FileNotFoundException $fnfe) {
  241.                     // The module directory has been deleted.
  242.                     // Log a warning, and delete the database entry.
  243.                     Tlog::getInstance()->addWarning(
  244.                         Translator::getInstance()->trans(
  245.                             'Module "%name%" directory was not found',
  246.                             ['%name%' => $module->getCode()]
  247.                         )
  248.                     );
  249.                 }
  250.                 $module->delete($con);
  251.                 $con->commit();
  252.                 $event->setModule($module);
  253.                 $this->cacheClear($dispatcher);
  254.             } catch (\Exception $e) {
  255.                 $con->rollBack();
  256.                 throw $e;
  257.             }
  258.         }
  259.     }
  260.     /**
  261.      * @param $eventName
  262.      */
  263.     public function update(ModuleEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  264.     {
  265.         if (null !== $module ModuleQuery::create()->findPk($event->getId())) {
  266.             $module
  267.                 ->setLocale($event->getLocale())
  268.                 ->setTitle($event->getTitle())
  269.                 ->setChapo($event->getChapo())
  270.                 ->setDescription($event->getDescription())
  271.                 ->setPostscriptum($event->getPostscriptum());
  272.             $module->save();
  273.             $event->setModule($module);
  274.         }
  275.     }
  276.     /**
  277.      * @param $eventName
  278.      *
  279.      * @throws \Exception
  280.      * @throws \Symfony\Component\Filesystem\Exception\IOException
  281.      * @throws \Exception
  282.      */
  283.     public function install(ModuleInstallEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  284.     {
  285.         $moduleDefinition $event->getModuleDefinition();
  286.         $oldModule ModuleQuery::create()->findOneByFullNamespace($moduleDefinition->getNamespace());
  287.         $fs = new Filesystem();
  288.         $activated false;
  289.         // check existing module
  290.         if (null !== $oldModule) {
  291.             $activated $oldModule->getActivate();
  292.             if ($activated) {
  293.                 // deactivate
  294.                 $toggleEvent = new ModuleToggleActivationEvent($oldModule->getId());
  295.                 // disable the check of the module because it's already done
  296.                 $toggleEvent->setNoCheck(true);
  297.                 $dispatcher->dispatch($toggleEventTheliaEvents::MODULE_TOGGLE_ACTIVATION);
  298.             }
  299.             // delete
  300.             $modulePath $oldModule->getAbsoluteBaseDir();
  301.             $deleteEvent = new ModuleDeleteEvent($oldModule);
  302.             try {
  303.                 $dispatcher->dispatch($deleteEventTheliaEvents::MODULE_DELETE);
  304.             } catch (Exception $ex) {
  305.                 // if module has not been deleted
  306.                 if ($fs->exists($modulePath)) {
  307.                     throw $ex;
  308.                 }
  309.             }
  310.         }
  311.         // move new module
  312.         $modulePath sprintf('%s%s'THELIA_MODULE_DIR$event->getModuleDefinition()->getCode());
  313.         try {
  314.             $fs->mirror($event->getModulePath(), $modulePath);
  315.         } catch (IOException $ex) {
  316.             if (!$fs->exists($modulePath)) {
  317.                 throw $ex;
  318.             }
  319.         }
  320.         // Update the module
  321.         $moduleDescriptorFile sprintf('%s%s%s%s%s'$modulePathDS'Config'DS'module.xml');
  322.         $moduleManagement = new ModuleManagement($this->container);
  323.         $file = new SplFileInfo($moduleDescriptorFile);
  324.         $module $moduleManagement->updateModule($file$this->container);
  325.         // activate if old was activated
  326.         if ($activated) {
  327.             $toggleEvent = new ModuleToggleActivationEvent($module->getId());
  328.             $toggleEvent->setNoCheck(true);
  329.             $dispatcher->dispatch($toggleEventTheliaEvents::MODULE_TOGGLE_ACTIVATION);
  330.         }
  331.         $event->setModule($module);
  332.     }
  333.     /**
  334.      * Call the payment method of the payment module of the given order.
  335.      *
  336.      * @throws \RuntimeException if no payment module can be found
  337.      */
  338.     public function pay(OrderPaymentEvent $event): void
  339.     {
  340.         $order $event->getOrder();
  341.         /* call pay method */
  342.         if (null === $paymentModule ModuleQuery::create()->findPk($order->getPaymentModuleId())) {
  343.             throw new \RuntimeException(
  344.                 Translator::getInstance()->trans(
  345.                     'Failed to find a payment Module with ID=%mid for order ID=%oid',
  346.                     [
  347.                         '%mid' => $order->getPaymentModuleId(),
  348.                         '%oid' => $order->getId(),
  349.                     ]
  350.                 )
  351.             );
  352.         }
  353.         $paymentModuleInstance $paymentModule->getPaymentModuleInstance($this->container);
  354.         $response $paymentModuleInstance->pay($order);
  355.         if (null !== $response && $response instanceof Response) {
  356.             $event->setResponse($response);
  357.         }
  358.     }
  359.     /**
  360.      * Changes position, selecting absolute ou relative change.
  361.      *
  362.      * @param $eventName
  363.      */
  364.     public function updatePosition(UpdatePositionEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  365.     {
  366.         $this->genericUpdatePosition(ModuleQuery::create(), $event$dispatcher);
  367.         $this->cacheClear($dispatcher);
  368.     }
  369.     protected function cacheClear(EventDispatcherInterface $dispatcher): void
  370.     {
  371.         $cacheEvent = new CacheEvent(
  372.             $this->container->getParameter('kernel.cache_dir')
  373.         );
  374.         $dispatcher->dispatch($cacheEventTheliaEvents::CACHE_CLEAR);
  375.     }
  376.     /**
  377.      * {@inheritdoc}
  378.      */
  379.     public static function getSubscribedEvents()
  380.     {
  381.         return [
  382.             TheliaEvents::MODULE_TOGGLE_ACTIVATION => [
  383.                 ['checkToggleActivation'255],
  384.                 ['toggleActivation'128],
  385.             ],
  386.             TheliaEvents::MODULE_UPDATE_POSITION => ['updatePosition'128],
  387.             TheliaEvents::MODULE_DELETE => ['delete'128],
  388.             TheliaEvents::MODULE_UPDATE => ['update'128],
  389.             TheliaEvents::MODULE_INSTALL => ['install'128],
  390.             TheliaEvents::MODULE_PAY => ['pay'128],
  391.         ];
  392.     }
  393. }