core/lib/Thelia/Action/Module.php line 202

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