Passed
Push — develop ( 4dafcf...5bbb09 )
by Julien
03:08
created

Package::hasWizardDir()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7.0986

Importance

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