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\InstallerInterface; |
14
|
|
|
use Backend\Core\Installer\ModuleInstaller; |
15
|
|
|
use Backend\Core\Language\Language as BL; |
16
|
|
|
use Console\Exceptions\InstallerClassNotFoundException; |
17
|
|
|
use Console\Exceptions\InstallerInterfaceException; |
18
|
|
|
use Console\Exceptions\ModuleAlreadyInstalledException; |
19
|
|
|
use Console\Exceptions\ModuleNotExistsException; |
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 install forkcms modules |
29
|
|
|
*/ |
30
|
|
|
class ModulesInstallCommand extends ModulesBaseInstallCommand |
31
|
|
|
{ |
32
|
|
|
|
33
|
|
|
protected function configure(): void |
34
|
|
|
{ |
35
|
|
|
$this->setName('forkcms:modules:install') |
36
|
|
|
->setDescription('Install 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 not 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\ModuleAlreadyInstalledException |
50
|
|
|
* @throws \Console\Exceptions\InstallerClassNotFoundException |
51
|
|
|
* @throws \Console\Exceptions\InstallerInterfaceException |
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_NOT_INSTALLED, [$this, 'installModulePromptFormat']); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
if (!empty($modules)) { |
72
|
|
|
foreach ($modules as $module) { |
73
|
|
|
// make sure this module can be installed |
74
|
|
|
$this->validateIfModuleCanBeInstalled($module); |
75
|
|
|
|
76
|
|
|
$toInstall = []; |
77
|
|
|
|
78
|
|
|
$this->findNotInstalledModuleDependencies($toInstall, $module); |
79
|
|
|
|
80
|
|
|
// dump($toInstall); |
|
|
|
|
81
|
|
|
|
82
|
|
|
if (empty($toInstall)) { |
83
|
|
|
$quest = "\n Are you sure you want to <comment>install</comment> module <info>$module</info>? (y/N) "; |
84
|
|
|
|
85
|
|
|
if ($this->confirmAction($quest)) { |
86
|
|
|
// do the actual install |
87
|
|
|
$this->installModule($module); |
88
|
|
|
|
89
|
|
|
$output->writeln("\n * <info>Module <comment>$module</comment> successful installed</info>.\n"); |
90
|
|
|
} else { |
91
|
|
|
$output->writeln(' Canceled.'); |
92
|
|
|
} |
93
|
|
|
} else { |
94
|
|
|
arsort($toInstall); |
95
|
|
|
|
96
|
|
|
$toInstallModules = implode(', ', array_keys($toInstall)); |
97
|
|
|
|
98
|
|
|
$quest = "\n Module <info>$module</info> depends on modules <comment>$toInstallModules</comment>." |
99
|
|
|
. "\n\n To <comment>install</comment> module <info>$module</info>, you will have to <comment>install</comment> modules <comment>$toInstallModules</comment>." |
100
|
|
|
. "\n Are you sure you want to do this? (y/N) "; |
101
|
|
|
|
102
|
|
|
if ($this->confirmAction($quest)) { |
103
|
|
|
|
104
|
|
|
$output->writeln(''); |
105
|
|
|
|
106
|
|
|
// Adding main module to install list |
107
|
|
|
$toInstall[$module] = 0; |
108
|
|
|
|
109
|
|
|
foreach ($toInstall as $toInstallModule => $priority) { |
110
|
|
|
// do the actual install |
111
|
|
|
$this->installModule($toInstallModule); |
112
|
|
|
|
113
|
|
|
$output->writeln(" * <info>Module <comment>$toInstallModule</comment> successful installed</info>."); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
$output->writeln(''); |
117
|
|
|
} else { |
118
|
|
|
$output->writeln(' Canceled.'); |
119
|
|
|
} |
120
|
|
|
} |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
// remove our container cache after this request |
124
|
|
|
(new Filesystem)->remove($this->getContainer()->getParameter('kernel.cache_dir')); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
return 0; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @param string $module |
132
|
|
|
* @return string |
133
|
|
|
* @throws \LogicException |
134
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException |
135
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException |
136
|
|
|
*/ |
137
|
|
|
public function installModulePromptFormat(string $module): string |
138
|
|
|
{ |
139
|
|
|
$module_pad = str_pad($module, 18, ' ', STR_PAD_RIGHT); |
140
|
|
|
|
141
|
|
|
$isInstalled = $this->isModuleInstalled($module); |
142
|
|
|
|
143
|
|
|
$installed = $isInstalled |
144
|
|
|
? '<info> ✔ installed</info>' |
145
|
|
|
: '<fg=magenta>not installed</>'; |
146
|
|
|
|
147
|
|
|
try { |
148
|
|
|
$this->validateIfModuleCanBeInstalled($module); |
149
|
|
|
|
150
|
|
|
$isCanBeInstalled = true; |
151
|
|
|
} catch (\Exception $e) { |
152
|
|
|
$isCanBeInstalled = false; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$canBeInstalled = $isCanBeInstalled |
156
|
|
|
? '' |
157
|
|
|
: '<error>can not be installed</error>'; |
158
|
|
|
|
159
|
|
|
return "$module_pad $installed $canBeInstalled"; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @param string $module |
164
|
|
|
* @throws \Console\Exceptions\ModuleNotExistsException |
165
|
|
|
* @throws \Console\Exceptions\ModuleAlreadyInstalledException |
166
|
|
|
* @throws \Console\Exceptions\InstallerClassNotFoundException |
167
|
|
|
* @throws \Console\Exceptions\InstallerInterfaceException |
168
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException |
169
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException |
170
|
|
|
*/ |
171
|
|
|
private function validateIfModuleCanBeInstalled(string $module): void |
172
|
|
|
{ |
173
|
|
|
// does the item exist |
174
|
|
|
if (!$this->existsModule($module)) { |
175
|
|
|
throw new ModuleNotExistsException($module); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
// already installed |
179
|
|
|
if (BackendModel::isModuleInstalled($module)) { |
180
|
|
|
throw new ModuleAlreadyInstalledException($module); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
$installerFile = BACKEND_MODULES_PATH . '/' . $module . '/Installer/Installer.php'; |
184
|
|
|
|
185
|
|
|
// no installer class present |
186
|
|
|
if (!is_file($installerFile)) { |
187
|
|
|
throw new InstallerClassNotFoundException($module, $installerFile); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$installer = $this->createInstaller($module); |
191
|
|
|
|
192
|
|
|
if (!($installer instanceof InstallerInterface) && !($installer instanceof ModuleInstaller)) { |
193
|
|
|
throw new InstallerInterfaceException($module, $installerFile); |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* @param string $module |
199
|
|
|
* @throws \LogicException |
200
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException |
201
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException |
202
|
|
|
* @throws \Symfony\Component\Console\Exception\InvalidArgumentException |
203
|
|
|
* @throws \Symfony\Component\Console\Exception\LogicException |
204
|
|
|
* @throws \Symfony\Component\Console\Exception\RuntimeException |
205
|
|
|
*/ |
206
|
|
|
private function installModule(string $module): void |
207
|
|
|
{ |
208
|
|
|
$installer = $this->createInstaller($module); |
209
|
|
|
$installer->setInput($this->input); |
210
|
|
|
$installer->setOutput($this->output); |
211
|
|
|
|
212
|
|
|
$variables = $installer->getPromptVariables(); |
213
|
|
|
|
214
|
|
|
if (!empty($variables)) { |
215
|
|
|
|
216
|
|
|
/** @var \Symfony\Component\Console\Helper\SymfonyQuestionHelper $helper */ |
217
|
|
|
$helper = $this->getHelper('question'); |
218
|
|
|
|
219
|
|
|
foreach ($variables as $variable => $desc) { |
220
|
|
|
|
221
|
|
|
$question = new Question($desc . ':'); |
222
|
|
|
$question->setMaxAttempts(1); |
223
|
|
|
|
224
|
|
|
$variables[$variable] = $helper->ask($this->input, $this->output, $question); |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
$installer->setVariables($variables); |
229
|
|
|
|
230
|
|
|
$installer->install(); |
231
|
|
|
|
232
|
|
|
// clear the cache so locale (and so much more) gets rebuilt |
233
|
|
|
$this->clearCache(); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* @param string $module |
238
|
|
|
* @param array $variables |
239
|
|
|
* @return \Backend\Core\Installer\InstallerInterface |
240
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException |
241
|
|
|
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException |
242
|
|
|
*/ |
243
|
|
|
private function createInstaller(string $module, array $variables = []): InstallerInterface |
244
|
|
|
{ |
245
|
|
|
$class = 'Backend\\Modules\\' . $module . '\\Installer\\Installer'; |
246
|
|
|
|
247
|
|
|
return new $class( |
248
|
|
|
BackendModel::getContainer()->get('database'), |
249
|
|
|
BL::getActiveLanguages(), |
250
|
|
|
array_keys(BL::getInterfaceLanguages()), |
251
|
|
|
false, |
252
|
|
|
$variables |
253
|
|
|
); |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.