*/ class Module extends BaseAction implements EventSubscriberInterface { /** @var ContainerInterface */ protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function toggleActivation(ModuleToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher) { if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) { $moduleInstance = $module->createInstance(); if (method_exists($moduleInstance, 'setContainer')) { $moduleInstance->setContainer($this->container); if ($module->getActivate() == BaseModule::IS_ACTIVATED) { $moduleInstance->deActivate($module); } else { $moduleInstance->activate($module); } } $event->setModule($module); $this->cacheClear($dispatcher); } } public function checkToggleActivation(ModuleToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher) { if (true === $event->isNoCheck()) { return; } if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) { try { if ($module->getActivate() == BaseModule::IS_ACTIVATED) { if ($module->getMandatory() == BaseModule::IS_MANDATORY && $event->getAssumeDeactivate() === false) { throw new \Exception( Translator::getInstance()->trans('Can\'t deactivate a secure module') ); } if ($event->isRecursive()) { $this->recursiveDeactivation($event, $eventName, $dispatcher); } $this->checkDeactivation($module); } else { if ($event->isRecursive()) { $this->recursiveActivation($event, $eventName, $dispatcher); } $this->checkActivation($module); } } catch (\Exception $ex) { $event->stopPropagation(); throw $ex; } } } /** * Check if module can be activated : supported version of Thelia, module dependencies. * * @param \Thelia\Model\Module $module * @throws Exception if activation fails. * @return bool true if the module can be activated, otherwise false */ private function checkActivation($module) { try { $moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir()); $moduleValidator->validate(false); } catch (\Exception $ex) { throw $ex; } return true; } /** * Check if module can be deactivated safely because other modules * could have dependencies to this module * * @param \Thelia\Model\Module $module * @return bool true if the module can be deactivated, otherwise false */ private function checkDeactivation($module) { $moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir()); $modules = $moduleValidator->getModulesDependOf(); if (\count($modules) > 0) { $moduleList = implode(', ', array_column($modules, 'code')); $message = (\count($modules) == 1) ? Translator::getInstance()->trans( '%s has dependency to module %s. You have to deactivate this module before.' ) : Translator::getInstance()->trans( '%s have dependencies to module %s. You have to deactivate these modules before.' ); throw new ModuleException( sprintf($message, $moduleList, $moduleValidator->getModuleDefinition()->getCode()) ); } return true; } /** * Get dependencies of the current module and activate it if needed * * @param ModuleToggleActivationEvent $event * @param $eventName * @param EventDispatcherInterface $dispatcher */ public function recursiveActivation(ModuleToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher) { if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) { $moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir()); $dependencies = $moduleValidator->getCurrentModuleDependencies(); foreach ($dependencies as $defMod) { $submodule = ModuleQuery::create() ->findOneByCode($defMod["code"]); if ($submodule && $submodule->getActivate() != BaseModule::IS_ACTIVATED) { $subevent = new ModuleToggleActivationEvent($submodule->getId()); $subevent->setRecursive(true); $dispatcher->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $subevent); } } } } /** * Get modules having current module in dependence and deactivate it if needed * * @param ModuleToggleActivationEvent $event * @param $eventName * @param EventDispatcherInterface $dispatcher */ public function recursiveDeactivation(ModuleToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher) { if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) { $moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir()); $dependencies = $moduleValidator->getModulesDependOf(true); foreach ($dependencies as $defMod) { $submodule = ModuleQuery::create() ->findOneByCode($defMod["code"]); if ($submodule && $submodule->getActivate() == BaseModule::IS_ACTIVATED) { $subevent = new ModuleToggleActivationEvent($submodule->getId()); $subevent->setRecursive(true); $dispatcher->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $subevent); } } } } public function delete(ModuleDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher) { $con = Propel::getWriteConnection(ModuleTableMap::DATABASE_NAME); $con->beginTransaction(); if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId(), $con)) { try { if (null === $module->getFullNamespace()) { throw new \LogicException( Translator::getInstance()->trans( 'Cannot instantiate module "%name%": the namespace is null. Maybe the model is not loaded ?', ['%name%' => $module->getCode()] ) ); } // If the module is referenced by an order, display a meaningful error // instead of 'delete cannot delete' caused by a constraint violation. // FIXME: we hav to find a way to delete modules used by order. if (OrderQuery::create()->filterByDeliveryModuleId($module->getId())->count() > 0 || OrderQuery::create()->filterByPaymentModuleId($module->getId())->count() > 0 ) { throw new \LogicException( Translator::getInstance()->trans( 'The module "%name%" is currently in use by at least one order, and can\'t be deleted.', ['%name%' => $module->getCode()] ) ); } try { if ($module->getMandatory() == BaseModule::IS_MANDATORY && $event->getAssumeDelete() === false) { throw new \Exception( Translator::getInstance()->trans('Can\'t remove a core module') ); } // First, try to create an instance $instance = $module->createInstance(); // Then, if module is activated, check if we can deactivate it if ($module->getActivate()) { // check for modules that depend of this one $this->checkDeactivation($module); } $instance->setContainer($this->container); $path = $module->getAbsoluteBaseDir(); $instance->destroy($con, $event->getDeleteData()); $fs = new Filesystem(); $fs->remove($path); } catch (\ReflectionException $ex) { // Happens probably because the module directory has been deleted. // Log a warning, and delete the database entry. Tlog::getInstance()->addWarning( Translator::getInstance()->trans( 'Failed to create instance of module "%name%" when trying to delete module. Module directory has probably been deleted', ['%name%' => $module->getCode()] ) ); } catch (FileNotFoundException $fnfe) { // The module directory has been deleted. // Log a warning, and delete the database entry. Tlog::getInstance()->addWarning( Translator::getInstance()->trans( 'Module "%name%" directory was not found', ['%name%' => $module->getCode()] ) ); } $module->delete($con); $con->commit(); $event->setModule($module); $this->cacheClear($dispatcher); } catch (\Exception $e) { $con->rollBack(); throw $e; } } } /** * @param ModuleEvent $event * @param $eventName * @param EventDispatcherInterface $dispatcher */ public function update(ModuleEvent $event, $eventName, EventDispatcherInterface $dispatcher) { if (null !== $module = ModuleQuery::create()->findPk($event->getId())) { $module ->setDispatcher($dispatcher) ->setLocale($event->getLocale()) ->setTitle($event->getTitle()) ->setChapo($event->getChapo()) ->setDescription($event->getDescription()) ->setPostscriptum($event->getPostscriptum()); $module->save(); $event->setModule($module); } } /** * @param \Thelia\Core\Event\Module\ModuleInstallEvent $event * @param $eventName * @param EventDispatcherInterface $dispatcher * * @throws \Exception * @throws \Symfony\Component\Filesystem\Exception\IOException * @throws \Exception */ public function install(ModuleInstallEvent $event, $eventName, EventDispatcherInterface $dispatcher) { $moduleDefinition = $event->getModuleDefinition(); $oldModule = ModuleQuery::create()->findOneByFullNamespace($moduleDefinition->getNamespace()); $fs = new Filesystem(); $activated = false; // check existing module if (null !== $oldModule) { $activated = $oldModule->getActivate(); if ($activated) { // deactivate $toggleEvent = new ModuleToggleActivationEvent($oldModule->getId()); // disable the check of the module because it's already done $toggleEvent->setNoCheck(true); $dispatcher->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $toggleEvent); } // delete $modulePath = $oldModule->getAbsoluteBaseDir(); $deleteEvent = new ModuleDeleteEvent($oldModule); try { $dispatcher->dispatch(TheliaEvents::MODULE_DELETE, $deleteEvent); } catch (Exception $ex) { // if module has not been deleted if ($fs->exists($modulePath)) { throw $ex; } } } // move new module $modulePath = sprintf('%s%s', THELIA_MODULE_DIR, $event->getModuleDefinition()->getCode()); try { $fs->mirror($event->getModulePath(), $modulePath); } catch (IOException $ex) { if (!$fs->exists($modulePath)) { throw $ex; } } // Update the module $moduleDescriptorFile = sprintf('%s%s%s%s%s', $modulePath, DS, 'Config', DS, 'module.xml'); $moduleManagement = new ModuleManagement($this->container); $file = new SplFileInfo($moduleDescriptorFile); $module = $moduleManagement->updateModule($file, $this->container); // activate if old was activated if ($activated) { $toggleEvent = new ModuleToggleActivationEvent($module->getId()); $toggleEvent->setNoCheck(true); $dispatcher->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $toggleEvent); } $event->setModule($module); } /** * Call the payment method of the payment module of the given order * * @param OrderPaymentEvent $event * * @throws \RuntimeException if no payment module can be found. */ public function pay(OrderPaymentEvent $event) { $order = $event->getOrder(); /* call pay method */ if (null === $paymentModule = ModuleQuery::create()->findPk($order->getPaymentModuleId())) { throw new \RuntimeException( Translator::getInstance()->trans( "Failed to find a payment Module with ID=%mid for order ID=%oid", [ "%mid" => $order->getPaymentModuleId(), "%oid" => $order->getId() ] ) ); } $paymentModuleInstance = $paymentModule->getPaymentModuleInstance($this->container); $response = $paymentModuleInstance->pay($order); if (null !== $response && $response instanceof Response) { $event->setResponse($response); } } /** * Changes position, selecting absolute ou relative change. * * @param UpdatePositionEvent $event * @param $eventName * @param EventDispatcherInterface $dispatcher */ public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher) { $this->genericUpdatePosition(ModuleQuery::create(), $event, $dispatcher); $this->cacheClear($dispatcher); } protected function cacheClear(EventDispatcherInterface $dispatcher) { $cacheEvent = new CacheEvent( $this->container->getParameter('kernel.cache_dir') ); $dispatcher->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent); } /** * @inheritdoc */ public static function getSubscribedEvents() { return [ TheliaEvents::MODULE_TOGGLE_ACTIVATION => [ ['checkToggleActivation', 255], ['toggleActivation', 128], ], TheliaEvents::MODULE_UPDATE_POSITION => ['updatePosition', 128], TheliaEvents::MODULE_DELETE => ['delete', 128], TheliaEvents::MODULE_UPDATE => ['update', 128], TheliaEvents::MODULE_INSTALL => ['install', 128], TheliaEvents::MODULE_PAY => ['pay', 128], ]; } }