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

ModulesBaseInstallCommand   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 280
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 0
loc 280
c 0
b 0
f 0
wmc 36
lcom 1
cbo 7
rs 8.8

14 Methods

Rating   Name   Duplication   Size   Complexity  
A execute() 0 5 1
A existsModule() 0 4 1
A clearCache() 0 4 1
B promptModule() 0 36 4
A confirmAction() 0 12 1
A fetchInstalledModules() 0 6 1
A getInstalledModules() 0 8 2
A isModuleInstalled() 0 4 1
A getModules() 0 6 1
A getModulesPath() 0 4 1
A findInstalledDependsModules() 0 14 4
A findNotInstalledModuleDependencies() 0 14 4
C findDependsModules() 0 28 8
B getModuleDependencies() 0 20 6
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\Modules\Extensions\Engine\Model as BackendExtensionsModel;
13
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Output\OutputInterface;
16
use Symfony\Component\Console\Question\ChoiceQuestion;
17
use Symfony\Component\Console\Question\ConfirmationQuestion;
18
19
/**
20
 * Base module installer\uninstaller class
21
 */
22
abstract class ModulesBaseInstallCommand extends ContainerAwareCommand
23
{
24
25
    const PROMPT_INSTALLED = 1;
26
    const PROMPT_NOT_INSTALLED = 0;
27
28
    /**
29
     * @var \Symfony\Component\Console\Input\InputInterface
30
     */
31
    protected $input;
32
33
    /**
34
     * @var \Symfony\Component\Console\Output\OutputInterface
35
     */
36
    protected $output;
37
38
    /**
39
     * @param \Symfony\Component\Console\Input\InputInterface $input
40
     * @param \Symfony\Component\Console\Output\OutputInterface $output
41
     * @return void
42
     */
43
    public function execute(InputInterface $input, OutputInterface $output)
44
    {
45
        $this->input = $input;
46
        $this->output = $output;
47
    }
48
49
    /**
50
     * @param string $module
51
     * @return bool
52
     */
53
    protected function existsModule(string $module): bool
54
    {
55
        return BackendExtensionsModel::existsModule($module);
56
    }
57
58
    /**
59
     * Clear cache
60
     */
61
    protected function clearCache(): void
62
    {
63
        BackendExtensionsModel::clearCache();
64
    }
65
66
    /**
67
     * @param int $mode
68
     * @param callable $formatter
69
     * @return array
70
     * @throws \LogicException
71
     * @throws \Symfony\Component\Console\Exception\LogicException
72
     * @throws \Symfony\Component\Console\Exception\RuntimeException
73
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
74
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
75
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
76
     */
77
    protected function promptModule(
78
        int $mode,
79
        callable $formatter
80
    ): array {
81
        $input = $this->input;
82
        $output = $this->output;
83
84
        $modules = $this->getModules();
85
86
        $showAllModules = false !== $input->getOption('show-all');
87
88
        if (!$showAllModules) {
89
            $modules = array_filter($modules, function ($module) use ($mode) {
90
                $isInstalled = $this->isModuleInstalled($module);
91
92
                return $mode === static::PROMPT_INSTALLED ? $isInstalled : !$isInstalled;
93
            });
94
95
            sort($modules);
96
        }
97
98
        $formattedModules = array_map($formatter, $modules);
99
100
        /** @var \Symfony\Component\Console\Helper\SymfonyQuestionHelper $helper */
101
        $helper = $this->getHelper('question');
102
        $question = new ChoiceQuestion('Select module:', $formattedModules);
103
        $question->setMaxAttempts(1);
104
105
        if ($module = $helper->ask($input, $output, $question)) {
106
            $index = array_search($module, $formattedModules, true);
107
108
            $modules = (array)$modules[$index];
109
        }
110
111
        return $modules;
112
    }
113
114
    protected function confirmAction(string $quest): bool
115
    {
116
        $input = $this->input;
117
        $output = $this->output;
118
119
        /** @var \Symfony\Component\Console\Helper\SymfonyQuestionHelper $helper */
120
        $helper = $this->getHelper('question');
121
        $question = new ConfirmationQuestion($quest, false);
122
        $question->setMaxAttempts(1);
123
124
        return $helper->ask($input, $output, $question);
125
    }
126
127
    /**
128
     * @return array|null
129
     * @throws \LogicException
130
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
131
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
132
     */
133
    protected function fetchInstalledModules(): ?array
134
    {
135
        $sql = 'SELECT m.name FROM modules AS m';
136
137
        return $this->getContainer()->get('database')->getColumn($sql);
138
    }
139
140
    /**
141
     * @var array|null
142
     */
143
    private $installedModules;
144
145
    /**
146
     * @return array|null
147
     * @throws \LogicException
148
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
149
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
150
     */
151
    protected function getInstalledModules(): ?array
152
    {
153
        if (null === $this->installedModules) {
154
            $this->installedModules = $this->fetchInstalledModules();
155
        }
156
157
        return $this->installedModules;
158
    }
159
160
    /**
161
     * @param string $module
162
     * @return bool
163
     * @throws \LogicException
164
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
165
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
166
     */
167
    protected function isModuleInstalled(string $module): bool
168
    {
169
        return in_array($module, $this->getInstalledModules(), true);
170
    }
171
172
    /**
173
     * @return array
174
     */
175
    protected function getModules(): array
176
    {
177
        return array_map(function ($path) {
178
            return basename($path);
179
        }, $this->getModulesPath());
180
    }
181
182
    /**
183
     * @return array
184
     */
185
    protected function getModulesPath(): array
186
    {
187
        return glob(BACKEND_MODULES_PATH . '/*', GLOB_ONLYDIR);
188
    }
189
190
    /**
191
     * The method finds all installed modules that depend on the desired module
192
     *
193
     * @param array $modules
194
     * @param string $module
195
     * @param int $level
196
     * @throws \LogicException
197
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
198
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
199
     */
200
    protected function findInstalledDependsModules(array &$modules, string $module, int $level = 0)
201
    {
202
        ++$level;
203
204
        $dependsModules = $this->findDependsModules($module);
205
206
        foreach ($dependsModules as $dependsModule) {
207
            if (!isset($modules[$dependsModule]) && $this->isModuleInstalled($dependsModule)) {
208
                $modules[$dependsModule] = $level;
209
210
                $this->findInstalledDependsModules($modules, $dependsModule, $level);
211
            }
212
        }
213
    }
214
215
    /**
216
     * The method finds all not installed modules depends on the desired module
217
     *
218
     * @param array $modules
219
     * @param string $module
220
     * @param int $level
221
     * @throws \LogicException
222
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
223
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
224
     */
225
    protected function findNotInstalledModuleDependencies(array &$modules, string $module, int $level = 0)
226
    {
227
        ++$level;
228
229
        $dependsModules = $this->getModuleDependencies($module);
230
231
        foreach ($dependsModules as $dependsModule) {
232
            if (!isset($modules[$dependsModule]) && !$this->isModuleInstalled($dependsModule)) {
233
                $modules[$dependsModule] = $level;
234
235
                $this->findNotInstalledModuleDependencies($modules, $dependsModule, $level);
236
            }
237
        }
238
    }
239
240
    /**
241
     * The method finds all modules that depend on the desired module
242
     *
243
     * @param string $needle
244
     * @return array
245
     */
246
    protected function findDependsModules(string $needle): array
247
    {
248
        $result = [];
249
250
        $modules = $this->getModulesPath();
251
252
        foreach ($modules as $modulePath) {
253
            $info = $modulePath . '/info.xml';
254
255
            if (file_exists($info) && is_readable($info)) {
256
                $xml = new \SimpleXMLElement(file_get_contents($info));
257
258
                $dependsOn = $xml->xpath('//depends_on/module');
259
260
                if (!empty($dependsOn) && is_array($dependsOn)) {
261
                    foreach ($dependsOn as $depends) {
262
                        if ($needle === (string)$depends) {
263
                            $result[] = basename($modulePath);
264
265
                            break;
266
                        }
267
                    }
268
                }
269
            }
270
        }
271
272
        return $result;
273
    }
274
275
    /**
276
     * The method finds all modules depends on the desired module
277
     *
278
     * @param string $module
279
     * @return array
280
     */
281
    protected function getModuleDependencies(string $module): array
282
    {
283
        $result = [];
284
285
        $info = BACKEND_MODULES_PATH . "/$module/info.xml";
286
287
        if (file_exists($info) && is_readable($info)) {
288
            $xml = new \SimpleXMLElement(file_get_contents($info));
289
290
            $dependsOn = $xml->xpath('//depends_on/module');
291
292
            if (!empty($dependsOn) && is_array($dependsOn)) {
293
                foreach ($dependsOn as $depends) {
294
                    $result[] = (string)$depends;
295
                }
296
            }
297
        }
298
299
        return $result;
300
    }
301
}
302