UniqueCommand::collectData()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.6737
c 0
b 0
f 0
cc 6
eloc 15
nc 6
nop 0
1
<?php
2
/**
3
 * Magento Version Identification
4
 *
5
 * PHP version 5
6
 *
7
 * @author    Steve Robbins <[email protected]>
8
 * @copyright 2015 Steve Robbins
9
 * @license   http://creativecommons.org/licenses/by/4.0/ CC BY 4.0
10
 * @link      https://github.com/steverobbins/magento-version-identification-php
11
 */
12
13
namespace Mvi\Command;
14
15
use Mvi\Command\MviCommand;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Output\OutputInterface;
18
19
/**
20
 * Finds unique md5 hashes
21
 */
22
class UniqueCommand extends MviCommand
23
{
24
    const VERSION_DESTINATION = 'version.json';
25
26
    const ACCURACY_FACTOR = 500;
27
28
    const EDITION_SHORT_ENTERPRISE = 'EE';
29
    const EDITION_SHORT_COMMUNITY  = 'CE';
30
    const EDITION_LONG_ENTERPRISE  = 'Enterprise';
31
    const EDITION_LONG_COMMUNITY   = 'Community';
32
33
    /**
34
     * Patterns of file that might not be reliable
35
     *
36
     * @var string[]
37
     */
38
    protected $fileIgnorePatterns = [
39
        "/\/rwd\//",
40
        "/[^(js|css)]$/",
41
    ];
42
43
    /**
44
     * Configure generate command
45
     *
46
     * @return void
47
     */
48
    protected function configure()
49
    {
50
        $this
51
            ->setName('unique')
52
            ->setDescription('Find unique md5 hashes and save to file');
53
    }
54
55
    /**
56
     * Run unique command
57
     *
58
     * @param InputInterface  $input
59
     * @param OutputInterface $output
60
     *
61
     * @return void
62
     */
63
    protected function execute(InputInterface $input, OutputInterface $output)
64
    {
65
        $data           = $this->collectData();
66
        $fileHashCounts = $this->buildFileHashCounts($data);
67
        $fingerprints   = [];
68
        $tries          = 0;
69
        while (count($data) > 0) {
70
            $accuracy = ceil(++$tries / self::ACCURACY_FACTOR);
71
            $file     = key($fileHashCounts);
72
            foreach ($data as $release => $value) {
73
                $this->identify($value, $file, $fileHashCounts, $accuracy, $fingerprints, $release, $data, $output);
74
            }
75
            next($fileHashCounts) ?: reset($fileHashCounts);
76
        }
77
        if ($this->saveUniqueVersions($fingerprints)) {
78
            $output->writeln(sprintf('Unique hashes written to <info>%s</info>', self::VERSION_DESTINATION));
79
        } else {
80
            $output->writeln('<error>Failed to write unique hashes to file</error>');
81
        }
82
    }
83
84
    /**
85
     * Collect all the release hash datas
86
     *
87
     * [
88
     *     'CE-1.0.0' => [
89
     *         'abc123' => 'foo.js',
90
     *         'edf456' => 'bar.js',
91
     *     ],
92
     *     'CE-1.1.0' => [ ... ]
93
     * ]
94
     *
95
     * @return array
96
     */
97
    protected function collectData()
98
    {
99
        $data = [];
100
        foreach (glob($this->baseDir . DS . self::DIR_MD5 . DS . 'magento-*') as $release) {
101
            $lines   = explode("\n", file_get_contents($release));
102
            $release = str_replace($this->baseDir . DS . self::DIR_MD5 . DS . 'magento-', '', $release);
103
            $data[$release] = [];
104
            foreach ($lines as $line) {
105
                if (strlen($line) === 0) {
106
                    continue;
107
                }
108
                list($hash, $file) = explode(' ', $line);
109
                foreach ($this->fileIgnorePatterns as $pattern) {
110
                    if (preg_match($pattern, $file)) {
111
                        continue 2;
112
                    }
113
                }
114
                $data[$release][$hash] = $file;
115
            }
116
        }
117
        return $data;
118
    }
119
120
    /**
121
     * Get the most import files determined by how many unique hashes they have
122
     *
123
     * [
124
     *     'skin/adminhtml/default/default/boxes.css' => [
125
     *         'abc123' => [
126
     *             'CE-1.0'
127
     *         ],
128
     *         'efg456' => [
129
     *             'CE-1.1.0',
130
     *             'CE-1.1.1'
131
     *         ],
132
     *         ...
133
     *     ],
134
     *     ...
135
     * ]
136
     *
137
     * @param array $data
138
     *
139
     * @return array
140
     */
141
    protected function buildFileHashCounts(array $data)
142
    {
143
        $counts = [];
144
        foreach ($data as $release => $value) {
145
            foreach ($value as $hash => $file) {
146
                if (!isset($counts[$file])) {
147
                    $counts[$file] = [];
148
                }
149
                if (!isset($counts[$file][$hash])) {
150
                    $counts[$file][$hash] = [];
151
                }
152
                $counts[$file][$hash][] = $release;
153
            }
154
        }
155
        uasort($counts, function ($a, $b) {
156
            return count($b) - count($a);
157
        });
158
        return $counts;
159
    }
160
161
    /**
162
     * Take the release short name and expand
163
     *
164
     * @param string $name
165
     * @param array  $existing
166
     *
167
     * @return string[]
168
     */
169
    protected function prepareReleaseName($name, &$existing)
170
    {
171
        list($edition, $version) = explode('-', $name);
172
        switch ($edition) {
173
            case self::EDITION_SHORT_ENTERPRISE:
174
                $edition = self::EDITION_LONG_ENTERPRISE;
175
                break;
176
            case self::EDITION_SHORT_COMMUNITY:
177
                $edition = self::EDITION_LONG_COMMUNITY;
178
                break;
179
        }
180
        if (!is_array($existing)) {
181
            $existing = [];
182
        }
183
        if (!isset($existing[$edition])) {
184
            $existing[$edition] = [];
185
        }
186
        $existing[$edition][] = $version;
187
    }
188
189
    /**
190
     * Add file/hash/release combo to fingerprints if accurate enough
191
     *
192
     * @param array           $value
193
     * @param string          $file
194
     * @param array           $fileHashCounts
195
     * @param string          $accuracy
196
     * @param array           $fingerprints
197
     * @param string          $release
198
     * @param array           $data
199
     * @param OutputInterface $output
200
     *
201
     * @return void
202
     */
203
    protected function identify(
204
        array $value,
205
        $file,
206
        array $fileHashCounts,
207
        $accuracy,
208
        &$fingerprints,
209
        $release,
210
        array &$data,
211
        OutputInterface $output
212
    ) {
213
        $fileHash = array_flip($value);
214
        if (isset($fileHash[$file]) && count($fileHashCounts[$file][$fileHash[$file]]) <= $accuracy) {
215
            if (!isset($fingerprints[$file])) {
216
                $fingerprints[$file] = [];
217
            }
218
            $this->prepareReleaseName($release, $fingerprints[$file][$fileHash[$file]]);
219
            $output->writeln(sprintf(
220
                '<info>%s</info> can be identified by <info>%s</info> with hash <info>%s</info>',
221
                $release,
222
                $file,
223
                $fileHash[$file]
224
            ));
225
            unset($data[$release]);
226
        }
227
    }
228
229
    /**
230
     * Save fingerprints to file
231
     *
232
     * @param array $fingerprints
233
     *
234
     * @return integer
235
     */
236
    protected function saveUniqueVersions($fingerprints)
237
    {
238
        uasort($fingerprints, function ($a, $b) {
239
            return count($b) - count($a);
240
        });
241
        $json = str_replace('\\/', '/', json_encode($fingerprints, JSON_PRETTY_PRINT));
242
        return file_put_contents($this->baseDir . DS . self::VERSION_DESTINATION, $json);
243
    }
244
}
245