Completed
Pull Request — master (#2233)
by Revin
64:12
created

validateIfModuleCanBeInstalled()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 25
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
c 0
b 0
f 0
rs 8.439
cc 6
eloc 11
nc 5
nop 1
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);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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