Passed
Push — master ( a48198...5f62be )
by Julien
02:48
created

Package   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 91.24%

Importance

Changes 0
Metric Value
dl 0
loc 325
ccs 125
cts 137
cp 0.9124
rs 8.8
c 0
b 0
f 0
wmc 45
lcom 1
cbo 1

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 3
A __get() 0 4 1
A __set() 0 4 1
A __isset() 0 4 1
A __unset() 0 4 1
A parseBool() 0 4 1
A fixBoolIfExist() 0 6 2
B collectMetadata() 0 33 8
A getMetadata() 0 4 1
B extractIfMissing() 0 31 6
B hasWizardDir() 0 25 6
A getThumbnails() 0 36 5
A getSnapshots() 0 19 4
A isCompatibleToArch() 0 5 2
A isCompatibleToFirmware() 0 4 1
A isBeta() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like Package often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Package, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SSpkS\Package;
4
5
/**
6
 * SPK Package class
7
 *
8
 * @property string $spk Path to SPK file
9
 * @property string $spk_url URL to SPK file
10
 * @property string $displayname Pretty printed name of package (falls back to $package if not present)
11
 * @property string $package Package name
12
 * @property string $version Package version
13
 * @property string $description Package description
14
 * @property string $maintainer Package maintainer
15
 * @property string $maintainer_url URL of maintainer's web page
16
 * @property string $distributor Package distributor
17
 * @property string $distributor_url URL of distributor's web page
18
 * @property array $arch List of supported architectures, or 'noarch'
19
 * @property array $thumbnail List of thumbnail files
20
 * @property array $thumbnail_url List of thumbnail URLs
21
 * @property array $snapshot List of screenshot files
22
 * @property array $snapshot_url List of screenshot URLs
23
 * @property bool $beta TRUE if this is a beta package.
24
 * @property string $firmware Minimum firmware needed on device.
25
 * @property string $install_dep_services Dependencies required by this package.
26
 * @property bool $silent_install Allow silent install
27
 * @property bool $silent_uninstall Allow silent uninstall
28
 * @property bool $silent_upgrade Allow silent upgrade
29
 * @property bool $qinst Allow silent install
30
 * @property bool $qupgrade Allow silent upgrade
31
 * @property bool $qstart Allow automatic start after install
32
 */
33
class Package
34
{
35
    private $config;
36
    private $filepath;
37
    private $filepathNoExt;
38
    private $filename;
39
    private $filenameNoExt;
40
    private $metafile;
41
    private $wizfile;
42
    private $nowizfile;
43
    private $metadata;
44
45
    /**
46
     * @param \SSpkS\Config $config Config object
47
     * @param string $filename Filename of SPK file
48
     */
49 17
    public function __construct(\SSpkS\Config $config, $filename)
50
    {
51 17
        $this->config = $config;
52 17
        if (!preg_match('/\.spk$/', $filename)) {
53 1
            throw new \Exception('File ' . $filename . ' doesn\'t have .spk extension!');
54
        }
55 16
        if (!file_exists($filename)) {
56 1
            throw new \Exception('File ' . $filename . ' not found!');
57
        }
58 15
        $this->filepath      = $filename;
59 15
        $this->filename      = basename($filename);
60 15
        $this->filenameNoExt = basename($filename, '.spk');
61 15
        $this->filepathNoExt = $this->config->paths['cache'] . $this->filenameNoExt;
62 15
        $this->metafile      = $this->filepathNoExt . '.nfo';
63 15
        $this->wizfile       = $this->filepathNoExt . '.wiz';
64 15
        $this->nowizfile     = $this->filepathNoExt . '.nowiz';
65
        // Make sure we have metadata available
66 15
        $this->collectMetadata();        
67 15
    }
68
69
    /**
70
     * Getter magic method.
71
     *
72
     * @param string $name Name of requested value.
73
     * @return mixed Requested value.
74
     */
75 9
    public function __get($name)
76
    {
77 9
        return $this->metadata[$name];
78
    }
79
80
    /**
81
     * Setter magic method.
82
     *
83
     * @param string $name Name of variable to set.
84
     * @param mixed $value Value to set.
85
     */
86 5
    public function __set($name, $value)
87
    {
88 5
        $this->metadata[$name] = $value;
89 5
    }
90
91
    /**
92
     * Isset feature magic method.
93
     *
94
     * @param string $name Name of requested value.
95
     * @return bool TRUE if value exists, FALSE otherwise.
96
     */
97 5
    public function __isset($name)
98
    {
99 5
        return isset($this->metadata[$name]);
100
    }
101
102
    /**
103
     * Unset feature magic method.
104
     *
105
     * @param string $name Name of value to unset.
106
     */
107 1
    public function __unset($name)
108
    {
109 1
        unset($this->metadata[$name]);
110 1
    }
111
112
    /**
113
     * Parses boolean value ('yes', '1', 'true') into
114
     * boolean type.
115
     *
116
     * @param mixed $value Input value
117
     * @return bool Boolean interpretation of $value.
118
     */
119 15
    public function parseBool($value)
120
    {
121 15
        return in_array($value, array('true', 'yes', '1', 1));
122
    }
123
124
    /**
125
     * Checks if given property $prop exists and converts it
126
     * into a boolean value.
127
     *
128
     * @param string $prop Property to convert
129
     */
130 15
    private function fixBoolIfExist($prop)
131
    {
132 15
        if (isset($this->metadata[$prop])) {
133 9
            $this->metadata[$prop] = $this->parseBool($this->metadata[$prop]);
134 9
        }
135 15
    }
136
137
    /**
138
     * Gathers metadata from package. Extracts INFO file if neccessary.
139
     */
140 15
    private function collectMetadata()
141
    {
142 15
        if (!is_null($this->metadata)) {
143
            // metadata already collected
144
            return;
145
        }
146 15
        $this->extractIfMissing('INFO', $this->metafile);
147 15
        $this->metadata = parse_ini_file($this->metafile);
148 15
        if (!isset($this->metadata['displayname'])) {
149 15
            $this->metadata['displayname'] = $this->metadata['package'];
150 15
        }
151 15
        $this->metadata['spk'] = $this->filepath;
152
153
        // Convert architecture(s) to array, as multiple architectures can be specified
154 15
        $this->metadata['arch'] = explode(' ', $this->metadata['arch']);
155
156 15
        $this->fixBoolIfExist('silent_install');
157 15
        $this->fixBoolIfExist('silent_uninstall');
158 15
        $this->fixBoolIfExist('silent_upgrade');
159
160 15
        if ($this->isBeta()) {
161 6
            $this->metadata['beta'] = true;
162 6
        } else {
163 15
            $this->metadata['beta'] = false;
164
        }
165
166 15
        $qValue = $this->hasWizardDir()? false : true;
167 15
        $this->metadata['thumbnail'] = $this->getThumbnails();
168 15
        $this->metadata['snapshot']  = $this->getSnapshots();
169 15
        $this->metadata['qinst']     = !empty($this->metadata['qinst'])? parseBool($this->metadata['qinst']):$qValue;
170 15
        $this->metadata['qupgrade']  = !empty($this->metadata['qupgrade'])? parseBool($this->metadata['qupgrade']):$qValue;
171 15
        $this->metadata['qstart']    = !empty($this->metadata['qstart'])? parseBool($this->metadata['qstart']):$qValue;
172 15
    }
173
174
    /**
175
     * Returns metadata for this package.
176
     *
177
     * @return array Metadata.
178
     */
179 3
    public function getMetadata()
180
    {
181 3
        return $this->metadata;
182
    }
183
      
184
    /**
185
     * Extracts $inPkgName from package to $targetFile, if it doesn't
186
     * already exist. Needs the phar.so extension and allow_url_fopen.
187
     *
188
     * @param string $inPkgName Filename in package
189
     * @param string $targetFile Path to destination
190
     * @throws \Exception if the file couldn't get extracted.
191
     * @return bool TRUE if successful or no action needed.
192
     */
193 15
    public function extractIfMissing($inPkgName, $targetFile)
194
    {
195 15
        if (file_exists($targetFile)) {
196
            // Everything in working order
197 8
            return true;
198
        }
199
        // Try to extract file
200 15
        $tmp_dir = sys_get_temp_dir();
201 15
        $free_tmp = disk_free_space($tmp_dir);
202 15
        if ($free_tmp < 2048) {
203
            throw new \Exception('TMP folder only has ' . $free_tmp . ' Bytes space available. Disk full!');
204
        }
205 15
        $free = disk_free_space(dirname($targetFile));
206 15
        if ($free < 2048) {
207
            throw new \Exception('Package folder only has ' . $free . ' Bytes space available. Disk full!');
208
        }
209
        try {
210 15
            $p = new \PharData($this->filepath, \Phar::CURRENT_AS_FILEINFO | \Phar::KEY_AS_FILENAME);
211 15
        } catch (\UnexpectedValueException $e) {
212
            rename($this->filepath, $this->filepath . '.invalid');
213
            throw new \Exception('Package ' . $this->filepath . ' not readable! Will be ignored in the future. Please try again!');
214
        }
215 15
        $tmpExtractedFilepath = $tmp_dir . DIRECTORY_SEPARATOR . $inPkgName;
216 15
        if (file_exists($tmpExtractedFilepath)) {
217
            // stale file from before - unlink first
218
            unlink($tmpExtractedFilepath);
219
        }
220 15
        $p->extractTo($tmp_dir, $inPkgName);
221 15
        rename($tmpExtractedFilepath, $targetFile);
222 15
        return true;
223
    }
224
225
    /**
226
     * Returns a true if the package contains WIZARD_UIFILES.
227
     *
228
     * @return bool Package has a wizard
229
     */
230 15
    public function hasWizardDir()
231
    {
232 15
        if (file_exists($this->wizfile)) {
233
            return true;
234
        }
235
236 15
        if (file_exists($this->nowizfile)) {
237 5
            return false;
238
        }
239
240
        try {
241 10
            $p = new \PharData($this->filepath, \Phar::CURRENT_AS_FILEINFO | \Phar::KEY_AS_FILENAME);
242 10
        } catch (\UnexpectedValueException $e) {
243
            rename($this->filepath, $this->filepath . '.invalid');
244
            throw new \Exception('Package ' . $this->filepath . ' not readable! Will be ignored in the future. Please try again!');
245
        }
246 10
        foreach ($p as $file) {
247 10
            if (substr($file, strrpos($file, '/') + 1) == 'WIZARD_UIFILES') {
248
                touch($this->wizfile);
249
                return true;
250
            }
251 10
        }
252 10
        touch($this->nowizfile);
253 10
        return false;
254
    }
255
256
    /**
257
     * Returns a list of thumbnails for the specified package.
258
     *
259
     * @param string $pathPrefix Prefix to put before file path
260
     * @return array List of thumbnail urls
261
     */
262 15
    public function getThumbnails($pathPrefix = '')
263
    {
264
        $thumbnailSources = array(
265
            '72' => array(
266 15
                'file' => 'PACKAGE_ICON.PNG',
267 15
                'info' => 'package_icon',
268 15
            ),
269
            '120' => array(
270 15
                'file' => 'PACKAGE_ICON_256.PNG',
271 15
                'info' => 'package_icon_256',
272 15
            ),
273 15
        );
274 15
        $thumbnails = array();
275 15
        foreach ($thumbnailSources as $size => $sourceList) {
276 15
            $thumbName = $this->filepathNoExt . '_thumb_' . $size . '.png';
277
            // Try to find file in package, otherwise check if defined in INFO
278
            try {
279 15
                $this->extractIfMissing($sourceList['file'], $thumbName);
280 15
            } catch (\Exception $e) {
281
                // Check if icon is in metadata
282 15
                if (isset($this->metadata[$sourceList['info']])) {
283 11
                    file_put_contents($thumbName, base64_decode($this->metadata[$sourceList['info']]));
284 11
                }
285
            }
286
287
            // Use $size px thumbnail, if available
288 15
            if (file_exists($thumbName)) {
289 15
                $thumbnails[] = $pathPrefix . $thumbName;
290 15
            } else {
291
                // Use theme's default pictures
292 15
                $themeUrl = $this->config->paths['themes'] . $this->config->site['theme'] . '/';
293 15
                $thumbnails[] = $pathPrefix . $themeUrl . 'images/default_package_icon_' . $size . '.png';
294
            }
295 15
        }
296 15
        return $thumbnails;
297
    }
298
299
    /**
300
     * Returns a list of screenshots for the specified package.
301
     *
302
     * @param string $pathPrefix Prefix to put before file path
303
     * @return array List of screenshots
304
     */
305 15
    public function getSnapshots($pathPrefix = '')
306
    {
307
        /* Let's first try to extract screenshots from package (SSpkS feature) */
308 15
        $i = 1;
309 15
        while (true) {
310
            try {
311 15
                $this->extractIfMissing('screen_' . $i . '.png', $this->filepathNoExt . '_screen_' . $i . '.png');
312 8
                $i++;
313 15
            } catch (\Exception $e) {
314 15
                break;
315
            }
316 8
        }
317 15
        $snapshots = array();
318
        // Add screenshots, if available
319 15
        foreach (glob($this->filepathNoExt . '*_screen_*.png') as $snapshot) {
320 8
            $snapshots[] = $pathPrefix . $snapshot;
321 15
        }
322 15
        return $snapshots;
323
    }
324
325
    /**
326
     * Checks compatibility to the given $arch-itecture.
327
     *
328
     * @param string $arch Architecture to check against (or "noarch")
329
     * @return bool TRUE if compatible, otherwise FALSE.
330
     */
331 1
    public function isCompatibleToArch($arch)
332
    {
333
        // TODO: Check arch family, too?
334 1
        return (in_array($arch, $this->metadata['arch']) || in_array('noarch', $this->metadata['arch']));
335
    }
336
337
    /**
338
     * Checks compatibility to the given firmware $version.
339
     *
340
     * @param string $version Target firmware version.
341
     * @return bool TRUE if compatible, otherwise FALSE.
342
     */
343 1
    public function isCompatibleToFirmware($version)
344
    {
345 1
        return version_compare($this->metadata['firmware'], $version, '<=');
346
    }
347
348
    /**
349
     * Checks if this package is a beta version or not.
350
     *
351
     * @return bool TRUE if this is a beta version, FALSE otherwise.
352
     */
353 15
    public function isBeta()
354
    {
355 15
        return (isset($this->metadata['beta']) && $this->parseBool($this->metadata['beta']));
356
    }
357
}
358