Passed
Push — master ( 6b85d3...94a120 )
by Whallysson
01:55 queued 11s
created

Cwebp::hasNiceSupport()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 16
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 5
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 16
rs 9.6111
1
<?php
2
3
namespace CodeBlog\ToWebP\Convert\Converters;
4
5
/**
6
 * Class Cwebp
7
 *
8
 * @author Whallysson Avelino <https://github.com/whallysson>
9
 * @package CodeBlog\ToWebP\Convert\Converters
10
 */
11
12
use CodeBlog\ToWebP\AbstractConverter;
13
use Exception;
14
15
class Cwebp extends AbstractConverter
16
{
17
    /**
18
     * System paths to look for cwebp binary
19
     *
20
     * @var array
21
     */
22
    private $defaultPaths = [
23
        '/usr/bin/cwebp',
24
        '/usr/local/bin/cwebp',
25
        '/usr/gnu/bin/cwebp',
26
        '/usr/syno/bin/cwebp',
27
    ];
28
29
    /**
30
     * OS-specific binaries included in this library
31
     *
32
     * @var mixed
33
     */
34
    private $binary = [
35
        'WinNT' => ['cwebp.exe', '49e9cb98db30bfa27936933e6fd94d407e0386802cb192800d9fd824f6476873'],
36
        'Darwin' => ['cwebp-mac12', 'a06a3ee436e375c89dbc1b0b2e8bd7729a55139ae072ed3f7bd2e07de0ebb379'],
37
        'SunOS' => ['cwebp-sol', '1febaffbb18e52dc2c524cda9eefd00c6db95bc388732868999c0f48deb73b4f'],
38
        'FreeBSD' => ['cwebp-fbsd', 'e5cbea11c97fadffe221fdf57c093c19af2737e4bbd2cb3cd5e908de64286573'],
39
        'Linux' => ['cwebp-linux', '916623e5e9183237c851374d969aebdb96e0edc0692ab7937b95ea67dc3b2568'],
40
    ][PHP_OS];
41
42
    /**
43
     * @return bool|mixed
44
     * @throws Exception
45
     */
46
    public function checkRequirements()
47
    {
48
        if (!function_exists('exec')) {
49
            throw new Exception('exec() is not enabled.');
50
        }
51
52
        return true;
53
    }
54
55
    /**
56
     * @return array
57
     * @throws Exception
58
     */
59
    public function setUpBinaries()
60
    {
61
        // Removes system paths if the corresponding binary doesn't exist
62
        $binaries = array_filter($this->defaultPaths, function($binary) {
63
            return file_exists($binary);
64
        });
65
66
        $binaryFile = __DIR__ . '/Binaries/' . $this->binary[0];
67
68
        // Throws an exception if binary file does not exist
69
        if (!file_exists($binaryFile)) {
70
            throw new Exception('Operating system is currently not supported: ' . PHP_OS);
71
        }
72
73
        // File exists, now generate its hash
74
        $binaryHash = hash_file('sha256', $binaryFile);
75
76
        // Throws an exception if binary file checksum & deposited checksum do not match
77
        if ($binaryHash != $this->binary[1]) {
78
            throw new Exception('Binary checksum is invalid.');
79
        }
80
81
        $binaries[] = $binaryFile;
82
83
        return $binaries;
84
    }
85
86
    /**
87
     * Checks if 'Nice' is available
88
     *
89
     * @return bool
90
     */
91
    public function hasNiceSupport()
92
    {
93
        exec("nice 2>&1", $niceOutput);
94
95
        if (is_array($niceOutput) && isset($niceOutput[0])) {
96
            if (preg_match('/usage/', $niceOutput[0]) || (preg_match('/^\d+$/', $niceOutput[0]))) {
97
                /*
98
                 * Nice is available - default niceness (+10)
99
                 * https://www.lifewire.com/uses-of-commands-nice-renice-2201087
100
                 * https://www.computerhope.com/unix/unice.htm
101
                 */
102
103
                return true;
104
            }
105
106
            return false;
107
        }
108
    }
109
110
    /**
111
     * @return bool|mixed
112
     */
113
    public function convert()
114
    {
115
        try {
116
            $this->checkRequirements();
117
118
            // Preparing array holding possible binaries
119
            $binaries = $this->setUpBinaries();
120
        } catch (Exception $e) {
121
            return false; // TODO: `throw` custom \Exception $e & handle it smoothly on top-level.
122
        }
123
124
        /*
125
         * Preparing options
126
         */
127
128
        // lossless PNG conversion
129
        $lossless = (
130
        $this->extension == 'png'
131
            ? '-lossless'
132
            : ''
133
        );
134
135
        // Built-in method option
136
        $method = '-m 6';
137
138
        // Metadata (all, exif, icc, xmp or none (default))
139
        // Comma-separated list of existing metadata to copy from input to output
140
        $metadata = (
141
        $this->strip
142
            ? '-metadata none'
143
            : '-metadata all'
144
        );
145
146
        // Built-in low memory option
147
        $lowMemory = '-low_memory';
148
149
        $optionsArray = [
150
            $lossless,
151
            $quality = '-q ' . $this->quality,
0 ignored issues
show
Unused Code introduced by
The assignment to $quality is dead and can be removed.
Loading history...
152
            $method,
153
            $metadata,
154
            $lowMemory,
155
            $input = $this->escapeFilename($this->source),
0 ignored issues
show
Unused Code introduced by
The assignment to $input is dead and can be removed.
Loading history...
156
            $output = '-o ' . $this->escapeFilename($this->destination),
0 ignored issues
show
Unused Code introduced by
The assignment to $output is dead and can be removed.
Loading history...
157
            $stderrRedirect = '2>&1',
0 ignored issues
show
Unused Code introduced by
The assignment to $stderrRedirect is dead and can be removed.
Loading history...
158
        ];
159
160
        $options = implode(' ', array_filter($optionsArray));
161
162
        $nice = (
163
        $this->hasNiceSupport()
164
            ? 'nice'
165
            : ''
166
        );
167
168
        $success = false;
169
170
        // Try all paths
171
        foreach ($binaries as $index => $binary) {
172
            $command = $nice . ' ' . $binary . ' ' . $options;
173
            exec($command, $result, $returnCode);
174
175
            if ($returnCode == 0) { // Everything okay!
176
                // cwebp sets file permissions to 664 but instead ..
177
                // .. $destination's parent folder's permissions should be used (except executable bits)
178
                $destinationParent = dirname($this->destination);
179
                $fileStatistics = stat($destinationParent);
180
181
                // Apply same permissions as parent folder but strip off the executable bits
182
                $permissions = $fileStatistics['mode'] & 0000666;
183
                chmod($this->destination, $permissions);
184
185
                $success = true;
186
                break;
187
            }
188
        }
189
190
        return $success;
191
    }
192
}
193