1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace N98\Magento\Command\MagentoConnect; |
4
|
|
|
|
5
|
|
|
use N98\Magento\Command\AbstractMagentoCommand; |
6
|
|
|
use SimpleXMLElement; |
7
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
8
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
9
|
|
|
use Symfony\Component\Console\Input\InputOption; |
10
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
11
|
|
|
|
12
|
|
|
class ValidateExtensionCommand extends AbstractMagentoCommand |
13
|
|
|
{ |
14
|
|
|
protected $_connectConfig = false; |
15
|
|
|
|
16
|
|
|
protected function configure() |
17
|
|
|
{ |
18
|
|
|
$this |
19
|
|
|
->setName('extension:validate') |
20
|
|
|
->addArgument('package', InputArgument::OPTIONAL, 'Package_Module to check') |
21
|
|
|
->addOption( |
22
|
|
|
'skip-file', |
23
|
|
|
null, |
24
|
|
|
InputOption::VALUE_NONE, |
25
|
|
|
'If set, command will skip reporting the existence of package files' |
26
|
|
|
) |
27
|
|
|
->addOption( |
28
|
|
|
'skip-hash', |
29
|
|
|
null, |
30
|
|
|
InputOption::VALUE_NONE, |
31
|
|
|
'If set, command will skip validating the package file hashes' |
32
|
|
|
) |
33
|
|
|
->addOption( |
34
|
|
|
'full-report', |
35
|
|
|
null, |
36
|
|
|
InputOption::VALUE_NONE, |
37
|
|
|
'If set, command will report on ALL package files' |
38
|
|
|
) |
39
|
|
|
->addOption( |
40
|
|
|
'include-default', |
41
|
|
|
null, |
42
|
|
|
InputOption::VALUE_NONE, |
43
|
|
|
'Include default packages that ship with Magento Connect' |
44
|
|
|
) |
45
|
|
|
->setDescription('Reads Magento Connect Config, and checks that installed package files are really there'); |
46
|
|
|
|
47
|
|
|
$help = <<<HELP |
48
|
|
|
Reads Magento Connect config, and checks that installed |
49
|
|
|
package files are really there. |
50
|
|
|
|
51
|
|
|
Magento Connect is Magento's built in package manager. It's |
52
|
|
|
notorious for failing to completly install extension files |
53
|
|
|
if file permissions are setup incorrectly, while telling a |
54
|
|
|
Connect user that everything is OK. |
55
|
|
|
|
56
|
|
|
This command scans a the list of packages installed via |
57
|
|
|
Magento Connect, and uses the package manifest to check |
58
|
|
|
|
59
|
|
|
1. If the file actually exists |
60
|
|
|
|
61
|
|
|
2. If the file hash matches the hash from the manifest |
62
|
|
|
|
63
|
|
|
A missing file indicates a package wasn't installed |
64
|
|
|
correctly. A non-matching hash *might* mean the file's been |
65
|
|
|
changed by another process, or *might* mean the file is from |
66
|
|
|
a previous package version, or *might* mean the extension |
67
|
|
|
packager failed to generate the hash correctly. |
68
|
|
|
|
69
|
|
|
This is the madness of using software that lies. |
70
|
|
|
|
71
|
|
|
HELP; |
72
|
|
|
$this->setHelp($help); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
76
|
|
|
{ |
77
|
|
|
$this->_init($output); |
78
|
|
|
|
79
|
|
|
$packages = array($input->getArgument('package')); |
80
|
|
|
if ($packages == array(null)) { |
81
|
|
|
$packages = $this->_getInstalledPackages(); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
$to_skip = array(); |
85
|
|
|
if (!$input->getOption('include-default')) { |
86
|
|
|
$to_skip = $this->_getBasePackages(); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
foreach ($packages as $package) { |
90
|
|
|
if (in_array($package, $to_skip)) { |
91
|
|
|
continue; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
$output->writeln( |
95
|
|
|
array( |
96
|
|
|
$package, |
97
|
|
|
'--------------------------------------------------', |
98
|
|
|
'', |
99
|
|
|
'', |
100
|
|
|
) |
101
|
|
|
); |
102
|
|
|
|
103
|
|
|
$this->_validateSpecificPackage($package, $output, $input); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
$output->writeln(''); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* @param string $name |
111
|
|
|
* @return array|bool |
112
|
|
|
*/ |
113
|
|
View Code Duplication |
protected function _getSpecificPackageConfig($name) |
|
|
|
|
114
|
|
|
{ |
115
|
|
|
$config = $this->_loadConfig(); |
116
|
|
|
$packages = $config['channels_by_name']['community']['packages']; |
117
|
|
|
|
118
|
|
|
return isset($packages[$name]) ? $packages[$name] : false; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* @param array $config |
123
|
|
|
* @return array |
124
|
|
|
*/ |
125
|
|
|
protected function _getExtensionFileListFromSpecificConfig($config) |
126
|
|
|
{ |
127
|
|
|
$xml = simplexml_load_string($config['xml']); |
128
|
|
|
$return = array(); |
129
|
|
|
foreach ($xml->contents->children() as $target) { |
130
|
|
|
$files = $target->xpath('//file'); |
131
|
|
|
$return = array(); |
132
|
|
|
foreach ($files as $file) { |
133
|
|
|
$path = $this->_getPathOfFileNodeToTarget($file); |
134
|
|
|
$return[$path] = (string) $file['hash']; |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
return $return; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* @param string $targetName |
143
|
|
|
* @return string |
144
|
|
|
*/ |
145
|
|
|
protected function _getBasePathFromTargetName($targetName) |
146
|
|
|
{ |
147
|
|
|
$paths = array( |
148
|
|
|
'mageetc' => 'app/etc', |
149
|
|
|
'magecommunity' => 'app/code/community', |
150
|
|
|
'magedesign' => 'app/design', |
151
|
|
|
'magelocale' => 'app/locale', |
152
|
|
|
'magelocal' => 'app/code/local', |
153
|
|
|
'magecore' => 'app/code/core', |
154
|
|
|
'magelib' => 'lib', |
155
|
|
|
'magemedia' => 'media', |
156
|
|
|
'mageskin' => 'skin', |
157
|
|
|
'mageweb' => '.', |
158
|
|
|
'magetest' => 'tests', |
159
|
|
|
'mage' => '.', |
160
|
|
|
); |
161
|
|
|
|
162
|
|
|
return $paths[$targetName]; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @param SimpleXMLElement $node |
167
|
|
|
* @param string $path |
168
|
|
|
* |
169
|
|
|
* @return string |
170
|
|
|
*/ |
171
|
|
View Code Duplication |
protected function _getPathOfFileNodeToTarget(SimpleXMLElement $node, $path = '') |
|
|
|
|
172
|
|
|
{ |
173
|
|
|
if ($node->getName() == 'target') { |
174
|
|
|
return $this->_getBasePathFromTargetName((string) $node['name']) . $path; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$path = '/' . $node['name'] . $path; |
178
|
|
|
$parent = $this->_getParentNode($node); |
179
|
|
|
|
180
|
|
|
return $this->_getPathOfFileNodeToTarget($parent, $path); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* @param SimpleXMLElement $node |
185
|
|
|
* @return mixed |
186
|
|
|
*/ |
187
|
|
|
protected function _getParentNode($node) |
188
|
|
|
{ |
189
|
|
|
$parent = $node->xpath(".."); |
190
|
|
|
|
191
|
|
|
return array_shift($parent); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* @param SimpleXmlElement $node |
196
|
|
|
* @param string $path |
197
|
|
|
* @return string |
198
|
|
|
*/ |
199
|
|
View Code Duplication |
protected function getPathOfFileNodeToTarget($node, $path = '') |
|
|
|
|
200
|
|
|
{ |
201
|
|
|
if ($node->getName() == 'target') { |
202
|
|
|
return $this->_getBasePathFromTargetName((string) $node['name']) . $path; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
$path = '/' . $node['name'] . $path; |
206
|
|
|
$parent = $this->_getParentNode($node); |
207
|
|
|
return $this->_getPathOfFileNodeToTarget($parent, $path); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* @param string $package |
212
|
|
|
* @param OutputInterface $output |
213
|
|
|
* @param InputInterface $input |
214
|
|
|
*/ |
215
|
|
|
protected function _validateSpecificPackage($package, $output, $input) |
216
|
|
|
{ |
217
|
|
|
$files = array(); |
218
|
|
|
$config = $this->_getSpecificPackageConfig($package); |
219
|
|
|
if ($config) { |
220
|
|
|
$files = $this->_getExtensionFileListFromSpecificConfig($config); |
|
|
|
|
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
$pathBase = \Mage::getBaseDir(); |
224
|
|
|
foreach ($files as $path => $hash) { |
225
|
|
|
$path = $pathBase . \DS . $path; |
226
|
|
|
$this->_optionOutput('Checking: ' . $path, 'full-report', $output, $input); |
227
|
|
|
|
228
|
|
|
if (file_exists($path)) { |
229
|
|
|
$this->_optionOutput(' Path: OK', array('full-report', 'file'), $output, $input); |
|
|
|
|
230
|
|
|
|
231
|
|
|
if ("" === $hash) { |
232
|
|
|
$this->_optionOutput(' Hash: EMPTY', array('full-report', 'hash'), $output, $input); |
|
|
|
|
233
|
|
|
} elseif (md5(file_get_contents($path)) === $hash) { |
234
|
|
|
$this->_optionOutput(' Hash: OK', array('full-report', 'hash'), $output, $input); |
|
|
|
|
235
|
|
|
} else { |
236
|
|
|
$this->_optionOutput('Problem: ' . $path, 'hash', $output, $input); |
237
|
|
|
$this->_optionOutput(' Hash: MISMATCH', 'hash', $output, $input); |
238
|
|
|
} |
239
|
|
|
} else { |
240
|
|
|
$this->_optionOutput('Problem: ' . $path, 'file', $output, $input); |
241
|
|
|
$this->_optionOutput(' Path: FILE NOT FOUND', 'file', $output, $input); |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* @param string $text |
248
|
|
|
* @param string $type |
249
|
|
|
* @param OutputInterface $output |
250
|
|
|
* @param InputInterface $input |
251
|
|
|
*/ |
252
|
|
|
protected function _optionOutput($text, $type, $output, $input) |
253
|
|
|
{ |
254
|
|
|
$type = is_array($type) ? $type : array($type); |
255
|
|
|
|
256
|
|
|
$skipHash = $input->getOption('skip-hash'); |
257
|
|
|
$skipFile = $input->getOption('skip-file'); |
258
|
|
|
$fullReport = $input->getOption('full-report'); |
259
|
|
|
|
260
|
|
|
if (in_array('full-report', $type) && !$fullReport) { |
261
|
|
|
return; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
if (in_array('hash', $type) && $skipHash) { |
265
|
|
|
return; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
if (in_array('file', $type) && $skipFile) { |
269
|
|
|
return; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
$output->writeln($text); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* @return bool|mixed |
277
|
|
|
*/ |
278
|
|
|
protected function _loadConfig() |
279
|
|
|
{ |
280
|
|
|
if (!$this->_connectConfig) { |
281
|
|
|
$this->_connectConfig = file_get_contents($this->_getDownloaderConfigPath()); |
|
|
|
|
282
|
|
|
$this->_connectConfig = gzuncompress($this->_connectConfig); |
|
|
|
|
283
|
|
|
$this->_connectConfig = unserialize($this->_connectConfig); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
return $this->_connectConfig; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* @return array |
291
|
|
|
*/ |
292
|
|
View Code Duplication |
protected function _getInstalledPackages() |
|
|
|
|
293
|
|
|
{ |
294
|
|
|
$config = $this->_loadConfig(); |
295
|
|
|
$packages = $config['channels_by_name']['community']['packages']; |
296
|
|
|
foreach ($packages as $package) { |
297
|
|
|
$return[] = $package['name']; |
|
|
|
|
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
return $return; |
|
|
|
|
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* @return string[] |
305
|
|
|
*/ |
306
|
|
|
protected function _getBasePackages() |
307
|
|
|
{ |
308
|
|
|
return array( |
309
|
|
|
'Cm_RedisSession', |
310
|
|
|
'Interface_Adminhtml_Default', |
311
|
|
|
'Interface_Frontend_Base_Default', |
312
|
|
|
'Interface_Frontend_Default', |
313
|
|
|
'Interface_Frontend_Rwd_Default', |
314
|
|
|
'Interface_Install_Default', |
315
|
|
|
'Lib_Cm', |
316
|
|
|
'Lib_Credis', |
317
|
|
|
'Lib_Google_Checkout', |
318
|
|
|
'Lib_Js_Calendar', |
319
|
|
|
'Lib_Js_Ext', |
320
|
|
|
'Lib_Js_Mage', |
321
|
|
|
'Lib_Js_Prototype', |
322
|
|
|
'Lib_Js_TinyMCE', |
323
|
|
|
'Lib_LinLibertineFont', |
324
|
|
|
'Lib_Mage', |
325
|
|
|
'Lib_Magento', |
326
|
|
|
'Lib_Phpseclib', |
327
|
|
|
'Lib_Varien', |
328
|
|
|
'Lib_ZF', |
329
|
|
|
'Lib_ZF_Locale', |
330
|
|
|
'Mage_All_Latest', |
331
|
|
|
'Mage_Centinel', |
332
|
|
|
'Mage_Compiler', |
333
|
|
|
'Mage_Core_Adminhtml', |
334
|
|
|
'Mage_Core_Modules', |
335
|
|
|
'Mage_Downloader', |
336
|
|
|
'Mage_Locale_de_DE', |
337
|
|
|
'Mage_Locale_en_US', |
338
|
|
|
'Mage_Locale_es_ES', |
339
|
|
|
'Mage_Locale_fr_FR', |
340
|
|
|
'Mage_Locale_nl_NL', |
341
|
|
|
'Mage_Locale_pt_BR', |
342
|
|
|
'Mage_Locale_zh_CN', |
343
|
|
|
'Magento_Mobile', |
344
|
|
|
'Phoenix_Moneybookers', |
345
|
|
|
); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* @param OutputInterface $output |
350
|
|
|
*/ |
351
|
|
|
protected function _init($output) |
352
|
|
|
{ |
353
|
|
|
$this->detectMagento($output); |
354
|
|
|
|
355
|
|
|
if (!$this->initMagento()) { |
356
|
|
|
return; |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* @return string |
362
|
|
|
*/ |
363
|
|
|
protected function _getDownloaderConfigPath() |
364
|
|
|
{ |
365
|
|
|
return \Mage::getBaseDir() . '/downloader/cache.cfg'; |
366
|
|
|
} |
367
|
|
|
} |
368
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.