Test Setup Failed
Push — fix-qinst ( 9bf464...bd0961 )
by Julien
02:51
created

Package::extractIfMissing()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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