Completed
Push — master ( b520c4...41a82a )
by Bjørn
02:59
created

Wpc::checkOperationality()   B

Complexity

Conditions 10
Paths 14

Size

Total Lines 47
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 15.9914

Importance

Changes 0
Metric Value
cc 10
eloc 27
nc 14
nop 0
dl 0
loc 47
ccs 14
cts 23
cp 0.6087
crap 15.9914
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace WebPConvert\Convert\Converters;
4
5
use WebPConvert\Convert\Converters\AbstractConverter;
6
use WebPConvert\Convert\Converters\ConverterTraits\CloudConverterTrait;
7
use WebPConvert\Convert\Converters\ConverterTraits\CurlTrait;
8
use WebPConvert\Convert\Converters\ConverterTraits\LosslessAutoTrait;
9
use WebPConvert\Convert\Exceptions\ConversionFailedException;
10
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
11
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
12
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\InvalidApiKeyException;
13
14
/**
15
 * Convert images to webp using Wpc (a cloud converter based on WebP Convert).
16
 *
17
 * @package    WebPConvert
18
 * @author     Bjørn Rosell <[email protected]>
19
 * @since      Class available since Release 2.0.0
20
 */
21
class Wpc extends AbstractConverter
22
{
23
    use CloudConverterTrait;
24
    use CurlTrait;
25
    use LosslessAutoTrait;
26
27 6
    public function passOnLosslessAuto()
28
    {
29
        // TODO: Either make this configurable or perhaps depend on api version
30 6
        return true;
31
    }
32
33 6
    protected function getOptionDefinitionsExtra()
34
    {
35
        return [
36 6
            ['api-version', 'number', 0],                     /* Can currently be 0 or 1 */
37
            ['secret', 'string', '', true],    /* only in api v.0 */
38
            ['api-key', 'string', '', true],   /* new in api v.1 (renamed 'secret' to 'api-key') */
39
            ['url', 'string', '', true, true],
40
            ['crypt-api-key-in-transfer', 'boolean', false],  /* new in api v.1 */
41
        ];
42
    }
43
44 2
    private static function createRandomSaltForBlowfish()
45
    {
46 2
        $salt = '';
47 2
        $validCharsForSalt = array_merge(
48 2
            range('A', 'Z'),
49 2
            range('a', 'z'),
50 2
            range('0', '9'),
51 2
            ['.', '/']
52
        );
53
54 2
        for ($i=0; $i<22; $i++) {
55 2
            $salt .= $validCharsForSalt[array_rand($validCharsForSalt)];
56
        }
57 2
        return $salt;
58
    }
59
60
    /**
61
     * Get api key from options or environment variable
62
     *
63
     * @return string  api key or empty string if none is set
64
     */
65 6
    private function getApiKey()
66
    {
67 6
        if ($this->options['api-version'] == 0) {
68 4
            if (!empty($this->options['secret'])) {
69 4
                return $this->options['secret'];
70
            }
71 2
        } elseif ($this->options['api-version'] == 1) {
72 2
            if (!empty($this->options['api-key'])) {
73 1
                return $this->options['api-key'];
74
            }
75
        }
76 2
        if (!empty(getenv('WPC_API_KEY'))) {
77 2
            return getenv('WPC_API_KEY');
78
        }
79
        return '';
80
    }
81
82
    /**
83
     * Get url from options or environment variable
84
     *
85
     * @return string  URL to WPC or empty string if none is set
86
     */
87 6
    private function getApiUrl()
88
    {
89 6
        if (!empty($this->options['url'])) {
90 4
            return $this->options['url'];
91
        }
92 2
        if (!empty(getenv('WPC_API_URL'))) {
93 2
            return getenv('WPC_API_URL');
94
        }
95
        return '';
96
    }
97
98
99
    /**
100
     * Check operationality of Wpc converter.
101
     *
102
     * @throws SystemRequirementsNotMetException  if system requirements are not met (curl)
103
     * @throws ConverterNotOperationalException   if key is missing or invalid, or quota has exceeded
104
     */
105 6
    public function checkOperationality()
106
    {
107
        // Check for curl requirements
108 6
        $this->checkOperationalityForCurlTrait();
109
110 6
        $options = $this->options;
111
112 6
        $apiVersion = $options['api-version'];
113
114 6
        if ($apiVersion == 0) {
115 4
            if (!empty($this->getApiKey())) {
116
                // if secret is set, we need md5() and md5_file() functions
117 4
                if (!function_exists('md5')) {
118
                    throw new ConverterNotOperationalException(
119
                        'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
120
                        'contents. ' .
121
                        'But the required md5() PHP function is not available.'
122
                    );
123
                }
124 4
                if (!function_exists('md5_file')) {
125
                    throw new ConverterNotOperationalException(
126
                        'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
127 4
                        'contents. But the required md5_file() PHP function is not available.'
128
                    );
129
                }
130
            }
131 2
        } elseif ($apiVersion == 1) {
132 2
            if ($options['crypt-api-key-in-transfer']) {
133 2
                if (!function_exists('crypt')) {
134
                    throw new ConverterNotOperationalException(
135
                        'Configured to crypt the api-key, but crypt() function is not available.'
136
                    );
137
                }
138
139 2
                if (!defined('CRYPT_BLOWFISH')) {
140
                    throw new ConverterNotOperationalException(
141
                        'Configured to crypt the api-key. ' .
142
                        'That requires Blowfish encryption, which is not available on your current setup.'
143
                    );
144
                }
145
            }
146
        }
147
148 6
        if ($this->getApiUrl() == '') {
149
            throw new ConverterNotOperationalException(
150
                'Missing URL. You must install Webp Convert Cloud Service on a server, ' .
151
                'or the WebP Express plugin for Wordpress - and supply the url.'
152
            );
153
        }
154 6
    }
155
156
    /*
157
    public function checkConvertability()
158
    {
159
        // check upload limits
160
        $this->checkConvertabilityCloudConverterTrait();
161
162
        // TODO: some from below can be moved up here
163
    }
164
    */
165
166 6
    private function createOptionsToSend()
167
    {
168 6
        $optionsToSend = $this->options;
169
170 6
        if ($this->isQualityDetectionRequiredButFailing()) {
171
            // quality was set to "auto", but we could not meassure the quality of the jpeg locally
172
            // Ask the cloud service to do it, rather than using what we came up with.
173
            $optionsToSend['quality'] = 'auto';
174
        } else {
175 6
            $optionsToSend['quality'] = $this->getCalculatedQuality();
176
        }
177
178 6
        unset($optionsToSend['converters']);
179 6
        unset($optionsToSend['secret']);
180 6
        unset($optionsToSend['api-key']);
181 6
        unset($optionsToSend['url']);
182
183 6
        return $optionsToSend;
184
    }
185
186 6
    private function createPostData()
187
    {
188 6
        $options = $this->options;
189
190
        $postData = [
191 6
            'file' => curl_file_create($this->source),
192 6
            'options' => json_encode($this->createOptionsToSend()),
193 6
            'servername' => (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '')
194
        ];
195
196 6
        $apiVersion = $options['api-version'];
197
198 6
        $apiKey = $this->getApiKey();
199
200 6
        if ($apiVersion == 0) {
201 4
            $postData['hash'] = md5(md5_file($this->source) . $apiKey);
202 2
        } elseif ($apiVersion == 1) {
203
            //$this->logLn('api key: ' . $apiKey);
204
205 2
            if ($options['crypt-api-key-in-transfer']) {
206 2
                $salt = self::createRandomSaltForBlowfish();
207 2
                $postData['salt'] = $salt;
208
209
                // Strip off the first 28 characters (the first 6 are always "$2y$10$". The next 22 is the salt)
210 2
                $postData['api-key-crypted'] = substr(crypt($apiKey, '$2y$10$' . $salt . '$'), 28);
211
            } else {
212
                $postData['api-key'] = $apiKey;
213
            }
214
        }
215 6
        return $postData;
216
    }
217
218 6
    protected function doActualConvert()
219
    {
220 6
        $ch = self::initCurl();
221
222
        //$this->logLn('api url: ' . $this->getApiUrl());
223
224 6
        curl_setopt_array($ch, [
225 6
            CURLOPT_URL => $this->getApiUrl(),
226 6
            CURLOPT_POST => 1,
227 6
            CURLOPT_POSTFIELDS => $this->createPostData(),
228 6
            CURLOPT_BINARYTRANSFER => true,
229 6
            CURLOPT_RETURNTRANSFER => true,
230 6
            CURLOPT_HEADER => false,
231 6
            CURLOPT_SSL_VERIFYPEER => false
232
        ]);
233
234 6
        $response = curl_exec($ch);
235 6
        if (curl_errno($ch)) {
236 1
            throw new ConverterNotOperationalException('Curl error:' . curl_error($ch));
237
        }
238
239
        // Check if we got a 404
240 5
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
241 5
        if ($httpCode == 404) {
242 1
            curl_close($ch);
243 1
            throw new ConversionFailedException(
244 1
                'WPC was not found at the specified URL - we got a 404 response.'
245
            );
246
        }
247
248
        // Check for empty response
249 4
        if (empty($response)) {
250
            throw new ConversionFailedException(
251
                'Error: Unexpected result. We got nothing back. ' .
252
                    'HTTP CODE: ' . $httpCode . '. ' .
253
                    'Content type:' . curl_getinfo($ch, CURLINFO_CONTENT_TYPE)
254
            );
255
        };
256
257
        // The WPC cloud service either returns an image or an error message
258
        // Images has application/octet-stream.
259
        // Verify that we got an image back.
260 4
        if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
261 2
            curl_close($ch);
262
263 2
            if (substr($response, 0, 1) == '{') {
264 1
                $responseObj = json_decode($response, true);
265 1
                if (isset($responseObj['errorCode'])) {
266 1
                    switch ($responseObj['errorCode']) {
267 1
                        case 0:
268
                            throw new ConverterNotOperationalException(
269
                                'There are problems with the server setup: "' .
270
                                $responseObj['errorMessage'] . '"'
271
                            );
272 1
                        case 1:
273 1
                            throw new InvalidApiKeyException(
274 1
                                'Access denied. ' . $responseObj['errorMessage']
275
                            );
276
                        default:
277
                            throw new ConversionFailedException(
278
                                'Conversion failed: "' . $responseObj['errorMessage'] . '"'
279
                            );
280
                    }
281
                }
282
            }
283
284
            // WPC 0.1 returns 'failed![error messag]' when conversion fails. Handle that.
285 1
            if (substr($response, 0, 7) == 'failed!') {
286
                throw new ConversionFailedException(
287
                    'WPC failed converting image: "' . substr($response, 7) . '"'
288
                );
289
            }
290
291 1
            $errorMsg = 'Error: Unexpected result. We did not receive an image. We received: "';
292 1
            $errorMsg .= str_replace("\r", '', str_replace("\n", '', htmlentities(substr($response, 0, 400))));
293 1
            throw new ConversionFailedException($errorMsg . '..."');
294
            //throw new ConverterNotOperationalException($response);
295
        }
296
297 2
        $success = file_put_contents($this->destination, $response);
298 2
        curl_close($ch);
299
300 2
        if (!$success) {
301
            throw new ConversionFailedException('Error saving file. Check file permissions');
302
        }
303 2
    }
304
}
305