Completed
Push — fix-qinst ( d8e76d )
by Julien
13:12
created

Package::hasWizardFile()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 15
rs 9.4285
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
    public function __construct(\SSpkS\Config $config, $filename)
45
    {
46
        $this->config = $config;
47
        if (!preg_match('/\.spk$/', $filename)) {
48
            throw new \Exception('File ' . $filename . ' doesn\'t have .spk extension!');
49
        }
50
        if (!file_exists($filename)) {
51
            throw new \Exception('File ' . $filename . ' not found!');
52
        }
53
        $this->filepath      = $filename;
54
        $this->filename      = basename($filename);
55
        $this->filenameNoExt = basename($filename, '.spk');
56
        $this->filepathNoExt = $this->config->paths['cache'] . $this->filenameNoExt;
57
        $this->metafile      = $this->filepathNoExt . '.nfo';
58
        $this->wizardsfile   = $this->filepathNoExt . '.wiz';
0 ignored issues
show
Documentation introduced by
The property wizardsfile does not exist on object<SSpkS\Package\Package>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
59
    }
60
61
    /**
62
     * Getter magic method.
63
     *
64
     * @param string $name Name of requested value.
65
     * @return mixed Requested value.
66
     */
67
    public function __get($name)
68
    {
69
        $this->collectMetadata();
70
        return $this->metadata[$name];
71
    }
72
73
    /**
74
     * Setter magic method.
75
     *
76
     * @param string $name Name of variable to set.
77
     * @param mixed $value Value to set.
78
     */
79
    public function __set($name, $value)
80
    {
81
        $this->collectMetadata();
82
        $this->metadata[$name] = $value;
83
    }
84
85
    /**
86
     * Isset feature magic method.
87
     *
88
     * @param string $name Name of requested value.
89
     * @return bool TRUE if value exists, FALSE otherwise.
90
     */
91
    public function __isset($name)
92
    {
93
        $this->collectMetadata();
94
        return isset($this->metadata[$name]);
95
    }
96
97
    /**
98
     * Unset feature magic method.
99
     *
100
     * @param string $name Name of value to unset.
101
     */
102
    public function __unset($name)
103
    {
104
        $this->collectMetadata();
105
        unset($this->metadata[$name]);
106
    }
107
108
    /**
109
     * Parses boolean value ('yes', '1', 'true') into
110
     * boolean type.
111
     *
112
     * @param mixed $value Input value
113
     * @return bool Boolean interpretation of $value.
114
     */
115
    public function parseBool($value)
116
    {
117
        return in_array($value, array('true', 'yes', '1', 1));
118
    }
119
120
    /**
121
     * Checks if given property $prop exists and converts it
122
     * into a boolean value.
123
     *
124
     * @param string $prop Property to convert
125
     */
126
    private function fixBoolIfExist($prop)
127
    {
128
        if (isset($this->metadata[$prop])) {
129
            $this->metadata[$prop] = $this->parseBool($this->metadata[$prop]);
130
        }
131
    }
132
133
    /**
134
     * Gathers metadata from package. Extracts INFO file if neccessary.
135
     */
136
    private function collectMetadata()
137
    {
138
        if (!is_null($this->metadata)) {
139
            // metadata already collected
140
            return;
141
        }
142
        $this->extractIfMissing('INFO', $this->metafile);
143
        $this->metadata = parse_ini_file($this->metafile);
144
        if (!isset($this->metadata['displayname'])) {
145
            $this->metadata['displayname'] = $this->metadata['package'];
146
        }
147
        $this->metadata['spk'] = $this->filepath;
148
149
        // Convert architecture(s) to array, as multiple architectures can be specified
150
        $this->metadata['arch'] = explode(' ', $this->metadata['arch']);
151
152
        $this->fixBoolIfExist('silent_install');
153
        $this->fixBoolIfExist('silent_uninstall');
154
        $this->fixBoolIfExist('silent_upgrade');
155
156
        if (isset($this->metadata['beta']) && in_array($this->metadata['beta'], array('true', '1', 'beta'))) {
157
            $this->metadata['beta'] = true;
158
        } else {
159
            $this->metadata['beta'] = false;
160
        }
161
162
        $this->metadata['thumbnail'] = $this->getThumbnails();
163
        $this->metadata['snapshot']  = $this->getSnapshots();
164
        $qValues = $this->hasWizardFile() ? false : true;
165
        $this->metadata['qinst']  = $qValues;
166
        $this->metadata['qupgrade']  = $qValues;
167
        $this->metadata['qstart']  = $qValues;
168
    }
169
170
    /**
171
     * Returns metadata for this package.
172
     *
173
     * @return array Metadata.
174
     */
175
    public function getMetadata()
176
    {
177
        $this->collectMetadata();
178
        return $this->metadata;
179
    }
180
181
    /**
182
     * Extracts $inPkgName from package to $targetFile, if it doesn't
183
     * already exist. Needs the phar.so extension and allow_url_fopen.
184
     *
185
     * @param string $inPkgName Filename in package
186
     * @param string $targetFile Path to destination
187
     * @throws \Exception if the file couldn't get extracted.
188
     * @return bool TRUE if successful or no action needed.
189
     */
190
    public function extractIfMissing($inPkgName, $targetFile)
191
    {
192
        if (file_exists($targetFile)) {
193
            // Everything in working order
194
            return true;
195
        }
196
        // Try to extract file
197
        $tmp_dir = sys_get_temp_dir();
198
        $free_tmp = disk_free_space($tmp_dir);
199
        if ($free_tmp < 2048) {
200
            throw new \Exception('TMP folder only has ' . $free_tmp . ' Bytes space available. Disk full!');
201
        }
202
        $free = disk_free_space(dirname($targetFile));
203
        if ($free < 2048) {
204
            throw new \Exception('Package folder only has ' . $free . ' Bytes space available. Disk full!');
205
        }
206
        try {
207
            $p = new \PharData($this->filepath, \Phar::CURRENT_AS_FILEINFO | \Phar::KEY_AS_FILENAME);
208
        } catch (\UnexpectedValueException $e) {
209
            rename($this->filepath, $this->filepath . '.invalid');
210
            throw new \Exception('Package ' . $this->filepath . ' not readable! Will be ignored in the future. Please try again!');
211
        }
212
        $tmpExtractedFilepath = $tmp_dir . DIRECTORY_SEPARATOR . $inPkgName;
213
        if (file_exists($tmpExtractedFilepath)) {
214
            // stale file from before - unlink first
215
            unlink($tmpExtractedFilepath);
216
        }
217
        $p->extractTo($tmp_dir, $inPkgName);
218
        rename($tmpExtractedFilepath, $targetFile);
219
        return true;
220
    }
221
222
    /**
223
     * Returns a list of thumbnails for the specified package.
224
     *
225
     * @param string $pathPrefix Prefix to put before file path
226
     * @return array List of thumbnail urls
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
227
     */
228
    public function hasWizardFile($pathPrefix = '')
0 ignored issues
show
Unused Code introduced by
The parameter $pathPrefix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
229
    {
230
        // Try to find file in package
231
        try {
232
            $this->extractIfMissing('WIZARD_UIFILES/install_uifile', $this->wizardsfile);
0 ignored issues
show
Documentation introduced by
The property wizardsfile does not exist on object<SSpkS\Package\Package>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
233
            return true;
234
        } catch (\Exception $e) {
235
            return false;
236
        } finally {
237
            if (file_exists($this->wizardsfile)) {
0 ignored issues
show
Documentation introduced by
The property wizardsfile does not exist on object<SSpkS\Package\Package>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
238
                unlink($this->wizardsfile);
0 ignored issues
show
Documentation introduced by
The property wizardsfile does not exist on object<SSpkS\Package\Package>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
239
            }
240
            return false;
241
        }
242
    }
243
244
    /**
245
     * Returns a list of thumbnails for the specified package.
246
     *
247
     * @param string $pathPrefix Prefix to put before file path
248
     * @return array List of thumbnail urls
249
     */
250
    public function getThumbnails($pathPrefix = '')
251
    {
252
        $thumbnailSources = array(
253
            '72' => array(
254
                'file' => 'PACKAGE_ICON.PNG',
255
                'info' => 'package_icon',
256
            ),
257
            '120' => array(
258
                'file' => 'PACKAGE_ICON_256.PNG',
259
                'info' => 'package_icon_256',
260
            ),
261
        );
262
        $thumbnails = array();
263
        foreach ($thumbnailSources as $size => $sourceList) {
264
            $thumbName = $this->filepathNoExt . '_thumb_' . $size . '.png';
265
            // Try to find file in package, otherwise check if defined in INFO
266
            try {
267
                $this->extractIfMissing($sourceList['file'], $thumbName);
268
            } catch (\Exception $e) {
269
                // Check if icon is in metadata
270
                $this->collectMetadata();
271
                if (isset($this->metadata[$sourceList['info']])) {
272
                    file_put_contents($thumbName, base64_decode($this->metadata[$sourceList['info']]));
273
                }
274
            }
275
276
            // Use $size px thumbnail, if available
277
            if (file_exists($thumbName)) {
278
                $thumbnails[] = $pathPrefix . $thumbName;
279
            } else {
280
                // Use theme's default pictures
281
                $themeUrl = $this->config->paths['themes'] . $this->config->site['theme'] . '/';
282
                $thumbnails[] = $pathPrefix . $themeUrl . 'images/default_package_icon_' . $size . '.png';
283
            }
284
        }
285
        return $thumbnails;
286
    }
287
288
    /**
289
     * Returns a list of screenshots for the specified package.
290
     *
291
     * @param string $pathPrefix Prefix to put before file path
292
     * @return array List of screenshots
293
     */
294
    public function getSnapshots($pathPrefix = '')
295
    {
296
        /* Let's first try to extract screenshots from package (SSpkS feature) */
297
        $i = 1;
298
        while (true) {
299
            try {
300
                $this->extractIfMissing('screen_' . $i . '.png', $this->filepathNoExt . '_screen_' . $i . '.png');
301
                $i++;
302
            } catch (\Exception $e) {
303
                break;
304
            }
305
        }
306
        $snapshots = array();
307
        // Add screenshots, if available
308
        foreach (glob($this->filepathNoExt . '*_screen_*.png') as $snapshot) {
309
            $snapshots[] = $pathPrefix . $snapshot;
310
        }
311
        return $snapshots;
312
    }
313
314
    /**
315
     * Checks compatibility to the given $arch-itecture.
316
     *
317
     * @param string $arch Architecture to check against (or "noarch")
318
     * @return bool TRUE if compatible, otherwise FALSE.
319
     */
320
    public function isCompatibleToArch($arch)
321
    {
322
        // Make sure we have metadata available
323
        $this->collectMetadata();
324
        // TODO: Check arch family, too?
325
        return (in_array($arch, $this->metadata['arch']) || in_array('noarch', $this->metadata['arch']));
326
    }
327
328
    /**
329
     * Checks compatibility to the given firmware $version.
330
     *
331
     * @param string $version Target firmware version.
332
     * @return bool TRUE if compatible, otherwise FALSE.
333
     */
334
    public function isCompatibleToFirmware($version)
335
    {
336
        $this->collectMetadata();
337
        return version_compare($this->metadata['firmware'], $version, '<=');
338
    }
339
340
    /**
341
     * Checks if this package is a beta version or not.
342
     *
343
     * @return bool TRUE if this is a beta version, FALSE otherwise.
344
     */
345
    public function isBeta()
346
    {
347
        $this->collectMetadata();
348
        return (isset($this->metadata['beta']) && $this->metadata['beta'] == true);
349
    }
350
}
351