Passed
Pull Request — master (#59)
by mon
07:20
created

MapUpdater::getMap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace FileEye\MimeMap;
4
5
use FileEye\MimeMap\Map\EmptyMap;
6
use FileEye\MimeMap\Map\MimeMapInterface;
7
8
/**
9
 * Compiles the MIME type to file extension map.
10
 */
11
class MapUpdater
12
{
13
    /**
14
     * The default, empty, base map to use for update.
15
     */
16
    const DEFAULT_BASE_MAP_CLASS = EmptyMap::class;
17
18
    /**
19
     * The map object to update.
20
     *
21
     * @var MimeMapInterface
22
     */
23
    protected $map;
24
25
    /**
26
     * Returns the default file with override commands to be executed.
27
     *
28
     * The YAML file provides an array of calls to MapHandler methods to be
29
     * executed sequentially. Each entry indicates the method to be invoked and
30
     * the arguments to be passed in.
31
     *
32
     * @return string
33
     */
34 1
    public static function getDefaultMapBuildFile(): string
35
    {
36 1
        return __DIR__ . '/../resources/default_map_build.yml';
37
    }
38
39
    /**
40
     * Returns the map object being updated.
41
     *
42
     * @return MimeMapInterface
43
     */
44 8
    public function getMap(): MimeMapInterface
45
    {
46 8
        return $this->map;
47
    }
48
49
    /**
50
     * Sets the map object to update.
51
     *
52
     * @param string $map_class
53
     *   The FQCN of the map to be updated.
54
     *
55
     * @return $this
56
     */
57 8
    public function selectBaseMap(string $map_class): MapUpdater
58
    {
59 8
        $this->map = MapHandler::map($map_class);
60 8
        $this->map->backup();
61 8
        return $this;
62
    }
63
64
    /**
65
     * Loads a new type-to-extension map reading from a file in Apache format.
66
     *
67
     * @param string $source_file
68
     *   The source file. The file must conform to the format in the Apache
69
     *   source code repository file where MIME types and file extensions are
70
     *   associated.
71
     *
72
     * @return string[]
73
     *   An array of error messages.
74
     *
75
     * @throws \RuntimeException
76
     *   If it was not possible to access the file.
77
     */
78 3
    public function loadMapFromApacheFile(string $source_file): array
79
    {
80 3
        $errors = [];
81
82 3
        $lines = @file($source_file);
83 3
        if ($lines === false) {
84
            throw new \RuntimeException("Failed accessing {$source_file}");
85
        }
86 3
        foreach ($lines as $line) {
87 2
            if ($line[0] == '#') {
88 2
                continue;
89
            }
90 2
            $line = preg_replace("#\\s+#", ' ', trim($line));
91 2
            if (is_string($line)) {
92 2
                $parts = explode(' ', $line);
93 2
                $type = array_shift($parts);
94 2
                foreach ($parts as $extension) {
95 2
                    $this->map->addTypeExtensionMapping($type, $extension);
96
                }
97
            } else {
98
                $errors[] = "Error processing line: $line";
99
            }
100
        }
101 3
        $this->map->sort();
102
103 3
        return $errors;
104
    }
105
106
    /**
107
     * Loads a new type-to-extension map reading from a Freedesktop.org file.
108
     *
109
     * @param string $source_file
110
     *   The source file. The file must conform to the format in the
111
     *   Freedesktop.org database.
112
     *
113
     * @return string[]
114
     *   An array of error messages.
115
     */
116 3
    public function loadMapFromFreedesktopFile(string $source_file): array
117
    {
118 3
        $errors = [];
119
120 3
        $contents = file_get_contents($source_file);
121 3
        if ($contents === false) {
122
            $errors[] = 'Failed loading file ' . $source_file;
123
            return $errors;
124
        }
125
126 3
        $xml = simplexml_load_string($contents);
127 3
        if ($xml === false) {
128
            $errors[] = 'Malformed XML in file ' . $source_file;
129
            return $errors;
130
        }
131
132 3
        $aliases = [];
133
134 3
        foreach ($xml as $node) {
135 2
            $exts = [];
136 2
            foreach ($node->glob as $glob) {
137 2
                $pattern = (string) $glob['pattern'];
138 2
                if ('*' != $pattern[0] || '.' != $pattern[1]) {
139 2
                    continue;
140
                }
141 2
                $exts[] = substr($pattern, 2);
142
            }
143 2
            if (empty($exts)) {
144 2
                continue;
145
            }
146
147 2
            $type = (string) $node['type'];
148
149
            // Add description.
150 2
            if (isset($node->comment)) {
151 2
                $this->map->addTypeDescription($type, (string) $node->comment[0]);
152
            }
153 2
            if (isset($node->acronym)) {
154 2
                $acronym = (string) $node->acronym;
155 2
                if (isset($node->{'expanded-acronym'})) {
156 2
                    $acronym .= ': ' . (string) $node->{'expanded-acronym'};
157
                }
158 2
                $this->map->addTypeDescription($type, $acronym);
159
            }
160
161
            // Add extensions.
162 2
            foreach ($exts as $ext) {
163 2
                $this->map->addTypeExtensionMapping($type, $ext);
164
            }
165
166
            // All aliases are accumulated and processed at the end of the
167
            // cycle to allow proper consistency checking on the completely
168
            // developed list of types.
169 2
            foreach ($node->alias as $alias) {
170 2
                $aliases[$type][] = (string) $alias['type'];
171
            }
172
        }
173
174
        // Add all the aliases, provide logging of errors.
175 3
        foreach ($aliases as $type => $a) {
176 2
            foreach ($a as $alias) {
177
                try {
178 2
                    $this->map->addTypeAlias($type, $alias);
179 1
                } catch (MappingException $e) {
180 1
                    $errors[] = $e->getMessage();
181
                }
182
            }
183
        }
184 3
        $this->map->sort();
185
186 3
        return $errors;
187
    }
188
189
    /**
190
     * Applies to the map an array of overrides.
191
     *
192
     * @param array<int,array{0: string, 1: array<string>}> $overrides
193
     *   The overrides to be applied.
194
     *
195
     * @return string[]
196
     *   An array of error messages.
197
     */
198 3
    public function applyOverrides(array $overrides): array
199
    {
200 3
        $errors = [];
201
202 3
        foreach ($overrides as $command) {
203
            try {
204 3
                $callable = [$this->map, $command[0]];
205 3
                assert(is_callable($callable));
206 3
                call_user_func_array($callable, $command[1]);
207 1
            } catch (MappingException $e) {
208 1
                $errors[] = $e->getMessage();
209
            }
210
        }
211 3
        $this->map->sort();
212
213 3
        return $errors;
214
    }
215
216
    /**
217
     * Updates the map at a destination PHP file.
218
     *
219
     * @return $this
220
     */
221 1
    public function writeMapToPhpClassFile(string $file): MapUpdater
222
    {
223 1
        $content = file_get_contents($file);
224 1
        if ($content === false) {
225
            throw new \RuntimeException('Failed loading file ' . $file);
226
        }
227
228 1
        $newContent = preg_replace(
229
            '#protected static \$map = (.+?);#s',
230 1
            "protected static \$map = " . preg_replace('/\s+$/m', '', var_export($this->map->getMapArray(), true)) . ";",
231
            $content
232
        );
233 1
        file_put_contents($file, $newContent);
234
235 1
        return $this;
236
    }
237
}
238