1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Console\Modules; |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file is part of Fork CMS. |
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
|
|
|
use Backend\Core\Engine\Model as BackendModel; |
13
|
|
|
use Backend\Core\Installer\ModuleUninstaller; |
14
|
|
|
use Backend\Core\Installer\UninstallerInterface; |
15
|
|
|
use Backend\Core\Language\Language as BL; |
16
|
|
|
use Console\Exceptions\ModuleNotExistsException; |
17
|
|
|
use Console\Exceptions\ModuleNotInstalledException; |
18
|
|
|
use Console\Exceptions\UninstallerClassNotFoundException; |
19
|
|
|
use Console\Exceptions\UninstallerInterfaceException; |
20
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
21
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
22
|
|
|
use Symfony\Component\Console\Input\InputOption; |
23
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
24
|
|
|
use Symfony\Component\Console\Question\Question; |
25
|
|
|
use Symfony\Component\Filesystem\Filesystem; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Console command for uninstall forkcms modules |
29
|
|
|
*/ |
30
|
|
|
class ModulesUninstallCommand extends ModulesBaseInstallCommand |
31
|
|
|
{ |
32
|
|
|
|
33
|
|
|
protected function configure(): void |
34
|
|
|
{ |
35
|
|
|
$this->setName('forkcms:modules:uninstall') |
36
|
|
|
->setDescription('Uninstall module') |
37
|
|
|
->addUsage('ModuleOne ModuleTwo') |
38
|
|
|
->addUsage('--show-all') |
39
|
|
|
->addArgument('modules-id', InputArgument::OPTIONAL | InputArgument::IS_ARRAY) |
40
|
|
|
->addOption('show-all', 'a', InputOption::VALUE_OPTIONAL, 'Show all modules (default only installed)', false); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @param \Symfony\Component\Console\Input\InputInterface $input |
45
|
|
|
* @param \Symfony\Component\Console\Output\OutputInterface $output |
46
|
|
|
* @return int |
47
|
|
|
* @throws \LogicException |
48
|
|
|
* @throws \Console\Exceptions\ModuleNotExistsException |
49
|
|
|
* @throws \Console\Exceptions\ModuleNotInstalledException |
50
|
|
|
* @throws \Console\Exceptions\UninstallerClassNotFoundException |
51
|
|
|
* @throws \Console\Exceptions\UninstallerInterfaceException |
52
|
|
|
* @throws \Symfony\Component\Console\Exception\LogicException |
53
|
|
|
* @throws \Symfony\Component\Console\Exception\RuntimeException |
54
|
|
|
* @throws \Symfony\Component\Console\Exception\InvalidArgumentException |
55
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException |
56
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException |
57
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException |
58
|
|
|
* @throws \Symfony\Component\Filesystem\Exception\IOException |
59
|
|
|
*/ |
60
|
|
|
public function execute(InputInterface $input, OutputInterface $output) |
61
|
|
|
{ |
62
|
|
|
parent::execute($input, $output); |
63
|
|
|
|
64
|
|
|
// get parameters |
65
|
|
|
$modules = $input->getArgument('modules-id'); |
66
|
|
|
|
67
|
|
|
if (empty($modules)) { |
68
|
|
|
$modules = $this->promptModule(static::PROMPT_INSTALLED, [$this, 'uninstallModulePromptFormat']); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
if (empty($modules)) { |
72
|
|
|
$output->writeln(' Module not selected.'); |
73
|
|
|
} else { |
74
|
|
|
foreach ($modules as $module) { |
75
|
|
|
// make sure this module can be installed |
76
|
|
|
$this->validateIfModuleCanBeUninstalled($module); |
77
|
|
|
|
78
|
|
|
$toRemove = []; |
79
|
|
|
|
80
|
|
|
$this->findInstalledDependsModules($toRemove, $module); |
81
|
|
|
|
82
|
|
|
if (empty($toRemove)) { |
83
|
|
|
$quest = "\n Are you sure you want to <error>remove</error> module <info>$module</info>?" |
84
|
|
|
. "\n <comment>This action can not be undone!</comment> (y/N) "; |
85
|
|
|
|
86
|
|
|
if ($this->confirmAction($quest)) { |
87
|
|
|
// do the actual install |
88
|
|
|
$this->uninstallModule($module); |
89
|
|
|
|
90
|
|
|
$output->writeln("\n * <info>Module <comment>$module</comment> successful uninstalled</info>.\n"); |
91
|
|
|
} else { |
92
|
|
|
$output->writeln(' Canceled.'); |
93
|
|
|
} |
94
|
|
|
} else { |
95
|
|
|
arsort($toRemove); |
96
|
|
|
|
97
|
|
|
$toRemoveModules = implode(', ', array_keys($toRemove)); |
98
|
|
|
|
99
|
|
|
$quest = "\n Module <info>$module</info> depends on modules <comment>$toRemoveModules</comment>." |
100
|
|
|
. "\n\n To <error>remove</error> module <info>$module</info>, you will have to <error>remove</error> modules <comment>$toRemoveModules</comment>." |
101
|
|
|
. "\n Are you sure you want to do this?" |
102
|
|
|
. "\n <comment>This action can not be undone!</comment> (y/N) "; |
103
|
|
|
|
104
|
|
|
if ($this->confirmAction($quest)) { |
105
|
|
|
|
106
|
|
|
$output->writeln(''); |
107
|
|
|
|
108
|
|
|
// Adding main module to remove list |
109
|
|
|
$toRemove[$module] = 0; |
110
|
|
|
|
111
|
|
|
foreach ($toRemove as $toRemoveModule => $priority) { |
112
|
|
|
// do the actual install |
113
|
|
|
$this->uninstallModule($toRemoveModule); |
114
|
|
|
|
115
|
|
|
$output->writeln(" * <info>Module <comment>$toRemoveModule</comment> successful uninstalled</info>."); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$output->writeln(''); |
119
|
|
|
} else { |
120
|
|
|
$output->writeln(' Canceled.'); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
// remove our container cache after this request |
126
|
|
|
(new Filesystem)->remove($this->getContainer()->getParameter('kernel.cache_dir')); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
return 0; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param string $module |
134
|
|
|
* @return string |
135
|
|
|
* @throws \LogicException |
136
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException |
137
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException |
138
|
|
|
*/ |
139
|
|
|
public function uninstallModulePromptFormat(string $module): string |
140
|
|
|
{ |
141
|
|
|
$module_pad = str_pad($module, 18, ' ', STR_PAD_RIGHT); |
142
|
|
|
|
143
|
|
|
$isInstalled = $this->isModuleInstalled($module); |
144
|
|
|
|
145
|
|
|
$installed = $isInstalled |
146
|
|
|
? '<info> ✔ installed</info>' |
147
|
|
|
: '<fg=magenta>not installed</>'; |
148
|
|
|
|
149
|
|
|
try { |
150
|
|
|
$this->validateIfModuleCanBeUninstalled($module); |
151
|
|
|
|
152
|
|
|
$isCanBeUninstalled = true; |
153
|
|
|
} catch (\Exception $e) { |
154
|
|
|
$isCanBeUninstalled = false; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
$canBeUninstalled = $isCanBeUninstalled |
158
|
|
|
? '' |
159
|
|
|
: '<error>can not be uninstalled</error>'; |
160
|
|
|
|
161
|
|
|
return "$module_pad $installed $canBeUninstalled"; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* @param string $module |
166
|
|
|
* @throws \Console\Exceptions\ModuleNotExistsException |
167
|
|
|
* @throws \Console\Exceptions\ModuleNotInstalledException |
168
|
|
|
* @throws \Console\Exceptions\UninstallerClassNotFoundException |
169
|
|
|
* @throws \Console\Exceptions\UninstallerInterfaceException |
170
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException |
171
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException |
172
|
|
|
*/ |
173
|
|
|
private function validateIfModuleCanBeUninstalled(string $module): void |
174
|
|
|
{ |
175
|
|
|
// does the item exist |
176
|
|
|
if (!$this->existsModule($module)) { |
177
|
|
|
throw new ModuleNotExistsException($module); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
// already installed |
181
|
|
|
if (!BackendModel::isModuleInstalled($module)) { |
182
|
|
|
throw new ModuleNotInstalledException($module); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
$uninstallerFile = BACKEND_MODULES_PATH . '/' . $module . '/Installer/Uninstaller.php'; |
186
|
|
|
|
187
|
|
|
// no installer class present |
188
|
|
|
if (!is_file($uninstallerFile)) { |
189
|
|
|
$installerFile = BACKEND_MODULES_PATH . '/' . $module . '/Installer/Installer.php'; |
190
|
|
|
|
191
|
|
|
if (!is_file($installerFile)) { |
192
|
|
|
throw new UninstallerClassNotFoundException($module, $uninstallerFile); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
$installer = $this->createUninstaller($module); |
197
|
|
|
|
198
|
|
|
if (!($installer instanceof UninstallerInterface) && !($installer instanceof ModuleUninstaller)) { |
199
|
|
|
throw new UninstallerInterfaceException($module, $uninstallerFile); |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* @param string $module |
205
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException |
206
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException |
207
|
|
|
* @throws \Symfony\Component\Console\Exception\InvalidArgumentException |
208
|
|
|
* @throws \Symfony\Component\Console\Exception\LogicException |
209
|
|
|
* @throws \Symfony\Component\Console\Exception\RuntimeException |
210
|
|
|
*/ |
211
|
|
|
private function uninstallModule(string $module): void |
212
|
|
|
{ |
213
|
|
|
$uninstaller = $this->createUninstaller($module); |
214
|
|
|
$uninstaller->setInput($this->input); |
215
|
|
|
$uninstaller->setOutput($this->output); |
216
|
|
|
|
217
|
|
|
$variables = $uninstaller->getPromptVariables(); |
218
|
|
|
|
219
|
|
|
if (!empty($variables)) { |
220
|
|
|
|
221
|
|
|
/** @var \Symfony\Component\Console\Helper\SymfonyQuestionHelper $helper */ |
222
|
|
|
$helper = $this->getHelper('question'); |
223
|
|
|
|
224
|
|
|
foreach ($variables as $variable => $desc) { |
225
|
|
|
|
226
|
|
|
$question = new Question($desc . ':'); |
227
|
|
|
$question->setMaxAttempts(1); |
228
|
|
|
|
229
|
|
|
$variables[$variable] = $helper->ask($this->input, $this->output, $question); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
$uninstaller->setVariables($variables); |
234
|
|
|
|
235
|
|
|
$uninstaller->uninstall(); |
236
|
|
|
|
237
|
|
|
// clear the cache so locale (and so much more) gets rebuilt |
238
|
|
|
$this->clearCache(); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* @param string $module |
243
|
|
|
* @param array $variables |
244
|
|
|
* @return \Backend\Core\Installer\UninstallerInterface |
245
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException |
246
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException |
247
|
|
|
*/ |
248
|
|
|
private function createUninstaller(string $module, array $variables = []): UninstallerInterface |
249
|
|
|
{ |
250
|
|
|
$searchClass = 'Backend\\Modules\\' . $module . '\\Installer\\Uninstaller'; |
251
|
|
|
|
252
|
|
|
if (class_exists($searchClass)) { |
253
|
|
|
$class = $searchClass; |
254
|
|
|
} else { |
255
|
|
|
$class = 'Backend\\Modules\\' . $module . '\\Installer\\Installer'; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
return new $class( |
259
|
|
|
BackendModel::getContainer()->get('database'), |
260
|
|
|
BL::getActiveLanguages(), |
261
|
|
|
array_keys(BL::getInterfaceLanguages()), |
262
|
|
|
false, |
263
|
|
|
$variables |
264
|
|
|
); |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
|