|
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; |
|
|
|
|
|
|
103
|
|
|
default: |
|
104
|
|
View Code Duplication |
if ($extension->getState() > 10) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
271
|
|
|
$installer->setContainer($this->container); |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
return $installer; |
|
275
|
|
|
} |
|
276
|
|
|
} |
|
277
|
|
|
|
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,dieorexitstatements that have been added for debug purposes.In the above example, the last
return falsewill never be executed, because a return statement has already been met in every possible execution path.