Completed
Push — master ( 18f428...b3bf32 )
by Craig
06:40
created

ExtensionHelper::getExtensionInstallerInstance()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 1
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Zikula package.
5
 *
6
 * Copyright Zikula Foundation - http://zikula.org/
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Zikula\ExtensionsModule\Helper;
13
14
use Symfony\Component\Console\Input\ArrayInput;
15
use Symfony\Component\Console\Output\NullOutput;
16
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
17
use Symfony\Component\DependencyInjection\ContainerInterface;
18
use Zikula\Bundle\CoreBundle\Console\Application;
19
use Zikula\Bundle\CoreBundle\HttpKernel\ZikulaKernel;
20
use Zikula\Common\Translator\TranslatorInterface;
21
use Zikula\Core\AbstractBundle;
22
use Zikula\Core\CoreEvents;
23
use Zikula\Core\Event\GenericEvent;
24
use Zikula\Core\Event\ModuleStateEvent;
25
use Zikula\Core\ExtensionInstallerInterface;
26
use Zikula\ExtensionsModule\Constant;
27
use Zikula\ExtensionsModule\Entity\ExtensionEntity;
28
use Zikula\ExtensionsModule\ExtensionEvents;
29
30
class ExtensionHelper
31
{
32
    const TYPE_SYSTEM = 3;
33
    const TYPE_MODULE = 2;
34
35
    /**
36
     * @var ContainerInterface
37
     */
38
    private $container;
39
40
    /**
41
     * @var TranslatorInterface
42
     */
43
    private $translator;
44
45
    /**
46
     * ExtensionHelper constructor.
47
     *
48
     * @param ContainerInterface $container
49
     */
50
    public function __construct(ContainerInterface $container)
51
    {
52
        $this->container = $container;
53
        $this->translator = $container->get('translator.default');
54
        $this->translator->setLocale('ZikulaExtensionsModule');
55
    }
56
57
    /**
58
     * Install an extension.
59
     *
60
     * @param ExtensionEntity $extension
61
     * @return bool
62
     */
63
    public function install(ExtensionEntity $extension)
64
    {
65
        if ($extension->getState() == Constant::STATE_NOTALLOWED) {
66
            throw new \RuntimeException($this->translator->__f('Error! No permission to install %s.', ['%s' => $extension->getName()]));
67
        } elseif ($extension->getState() > 10) {
68
            throw new \RuntimeException($this->translator->__f('Error! %s is not compatible with this version of Zikula.', ['%s' => $extension->getName()]));
69
        }
70
71
        $bundle = $this->container->get('kernel')->getBundle($extension->getName());
72
73
        $installer = $this->getExtensionInstallerInstance($bundle);
74
        $result = $installer->install();
75
        if (!$result) {
76
            return false;
77
        }
78
        $this->container->get('zikula_extensions_module.extension_state_helper')->updateState($extension->getId(), Constant::STATE_ACTIVE);
79
80
        // clear the cache before calling events
81
        /** @var $cacheClearer \Zikula\Bundle\CoreBundle\CacheClearer */
82
        $cacheClearer = $this->container->get('zikula.cache_clearer');
83
        $cacheClearer->clear('symfony.config');
84
85
        $event = new ModuleStateEvent($bundle, $extension->toArray());
86
        $this->container->get('event_dispatcher')->dispatch(CoreEvents::MODULE_INSTALL, $event);
87
88
        return true;
89
    }
90
91
    /**
92
     * Upgrade an extension.
93
     *
94
     * @param ExtensionEntity $extension
95
     * @return bool
96
     */
97
    public function upgrade(ExtensionEntity $extension)
98
    {
99
        switch ($extension->getState()) {
100
            case Constant::STATE_NOTALLOWED:
101
                throw new \RuntimeException($this->translator->__f('Error! Not allowed to upgrade %s.', ['%s' => $extension->getDisplayname()]));
102
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
103
            default:
104 View Code Duplication
                if ($extension->getState() > 10) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105
                    throw new \RuntimeException($this->translator->__f('Error! %s is not compatible with this version of Zikula.', ['%s' => $extension->getDisplayname()]));
106
                }
107
        }
108
109
        $bundle = $this->container->get('kernel')->getModule($extension->getName());
110
111
112
        // @TODO: Need to check status of Dependencies here to be sure they are met for upgraded extension.
113
114
        $installer = $this->getExtensionInstallerInstance($bundle);
115
        $result = $installer->upgrade($extension->getVersion());
116
        if (is_string($result)) {
117
            if ($result != $extension->getVersion()) {
118
                // persist the last successful updated version
119
                $extension->setVersion($result);
120
                $this->container->get('doctrine')->getManager()->flush();
121
            }
122
123
            return false;
124
        } elseif (true !== $result) {
125
            return false;
126
        }
127
        // persist the updated version
128
        $newVersion = $bundle->getMetaData()->getVersion();
129
        $extension->setVersion($newVersion);
130
        $this->container->get('doctrine')->getManager()->flush();
131
132
        $this->container->get('zikula_extensions_module.extension_state_helper')->updateState($extension->getId(), Constant::STATE_ACTIVE);
133
134
        $this->container->get('zikula.cache_clearer')->clear('symfony');
135
136
        if ($this->container->getParameter('installed')) {
137
            // Upgrade succeeded, issue event.
138
            $event = new ModuleStateEvent($bundle, $extension->toArray());
139
            $this->container->get('event_dispatcher')->dispatch(CoreEvents::MODULE_UPGRADE, $event);
140
        }
141
142
        return true;
143
    }
144
145
    /**
146
     * Uninstall an extension.
147
     *
148
     * @param ExtensionEntity $extension
149
     * @return bool
150
     */
151
    public function uninstall(ExtensionEntity $extension)
152
    {
153
        if ($extension->getState() == Constant::STATE_NOTALLOWED
154
            || (ZikulaKernel::isCoreModule($extension->getName()))) {
155
            throw new \RuntimeException($this->translator->__f('Error! No permission to uninstall %s.', ['%s' => $extension->getDisplayname()]));
156
        }
157 View Code Duplication
        if ($extension->getState() == Constant::STATE_UNINITIALISED) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
158
            throw new \RuntimeException($this->translator->__f('Error! %s is not yet installed, therefore it cannot be uninstalled.', ['%s' => $extension->getDisplayname()]));
159
        }
160
161
        // allow event to prevent extension removal
162
        $vetoEvent = new GenericEvent($extension);
163
        $this->container->get('event_dispatcher')->dispatch(ExtensionEvents::REMOVE_VETO, $vetoEvent);
164
        if ($vetoEvent->isPropagationStopped()) {
165
            return false;
166
        }
167
168
        $bundle = $this->container->get('kernel')->getBundle($extension->getName());
169
170
        // remove hooks
171
        $this->container->get('zikula_hook_bundle.api.hook')->uninstallProviderHooks($bundle->getMetaData());
172
        $this->container->get('zikula_hook_bundle.api.hook')->uninstallSubscriberHooks($bundle->getMetaData());
173
174
        $installer = $this->getExtensionInstallerInstance($bundle);
175
        $result = $installer->uninstall();
176
        if (!$result) {
177
            return false;
178
        }
179
180
        // remove remaining extension variables
181
        $this->container->get('zikula_extensions_module.api.variable')->delAll($extension->getName());
182
183
        // remove the entry from the modules table
184
        $this->container->get('doctrine')->getManager()->getRepository('ZikulaExtensionsModule:ExtensionEntity')->removeAndFlush($extension);
185
186
        // clear the cache before calling events
187
        /** @var $cacheClearer \Zikula\Bundle\CoreBundle\CacheClearer */
188
        $cacheClearer = $this->container->get('zikula.cache_clearer');
189
        $cacheClearer->clear('symfony.config');
190
191
        $event = new ModuleStateEvent($bundle, $extension->toArray());
192
        $this->container->get('event_dispatcher')->dispatch(CoreEvents::MODULE_REMOVE, $event);
193
194
        return true;
195
    }
196
197
    /**
198
     * Uninstall an array of extensions.
199
     *
200
     * @param ExtensionEntity[] $extensions
201
     * @return bool
202
     */
203
    public function uninstallArray(array $extensions)
204
    {
205
        foreach ($extensions as $extension) {
206
            if (!$extension instanceof ExtensionEntity) {
207
                throw new \InvalidArgumentException();
208
            }
209
            $result = $this->uninstall($extension);
210
            if (!$result) {
211
                return false;
212
            }
213
        }
214
215
        return true;
216
    }
217
218
    /**
219
     * Based on the state of the extension, either install, upgrade or activate the extension.
220
     *
221
     * @param ExtensionEntity $extension
222
     * @return bool
223
     */
224
    public function enableExtension(ExtensionEntity $extension)
225
    {
226
        switch ($extension->getState()) {
227
            case Constant::STATE_UNINITIALISED:
228
                return $this->install($extension);
229
            case Constant::STATE_UPGRADED:
230
                return $this->upgrade($extension);
231
            case Constant::STATE_INACTIVE:
232
                return $this->container->get('zikula_extensions_module.extension_state_helper')->updateState($extension->getId(), Constant::STATE_ACTIVE);
233
            default:
234
                return false;
235
        }
236
    }
237
238
    /**
239
     * Run the console command app/console assets:install
240
     *
241
     * @throws \Exception
242
     */
243
    public function installAssets()
244
    {
245
        $kernel = $this->container->get('kernel');
246
        $application = new Application($kernel);
247
        $application->setAutoExit(false);
248
        $input = new ArrayInput([
249
            'command' => 'assets:install'
250
        ]);
251
        $output = new NullOutput();
252
        $application->run($input, $output);
253
    }
254
255
    /**
256
     * Get an instance of an extension Installer.
257
     *
258
     * @param AbstractBundle $bundle
259
     * @return ExtensionInstallerInterface
260
     */
261
    private function getExtensionInstallerInstance(AbstractBundle $bundle)
262
    {
263
        $className = $bundle->getInstallerClass();
264
        $reflectionInstaller = new \ReflectionClass($className);
265
        if (!$reflectionInstaller->isSubclassOf('\Zikula\Core\ExtensionInstallerInterface')) {
266
            throw new \RuntimeException($this->translator->__f("%s must implement ExtensionInstallerInterface", ['%s' => $className]));
267
        }
268
        $installer = $reflectionInstaller->newInstance();
269
        $installer->setBundle($bundle);
270
        if ($installer instanceof ContainerAwareInterface) {
1 ignored issue
show
Bug introduced by
The class Symfony\Component\Depend...ContainerAwareInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
271
            $installer->setContainer($this->container);
272
        }
273
274
        return $installer;
275
    }
276
}
277