Completed
Pull Request — 2.3 (#2410)
by Franck
07:33
created

Module::checkToggleActivation()   D

Complexity

Conditions 9
Paths 17

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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