Passed
Push — master ( fe4cdf...8df6f6 )
by Markus
02:35
created

Package   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 293
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 95.12%

Importance

Changes 16
Bugs 1 Features 0
Metric Value
c 16
b 1
f 0
dl 0
loc 293
ccs 117
cts 123
cp 0.9512
rs 8.8
wmc 36
lcom 1
cbo 1

15 Methods

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