Passed
Push — master ( 5cb744...41c0c2 )
by Bjørn
01:36 queued 11s
created

Ewww::checkOperationality()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7.5375

Importance

Changes 6
Bugs 1 Features 0
Metric Value
cc 7
eloc 22
c 6
b 1
f 0
nc 7
nop 0
dl 0
loc 36
ccs 14
cts 18
cp 0.7778
crap 7.5375
rs 8.6346
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\Exceptions\ConversionFailedException;
9
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
10
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\InvalidApiKeyException;
11
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
12
use WebPConvert\Options\SensitiveStringOption;
13
14
/**
15
 * Convert images to webp using ewww cloud service.
16
 *
17
 * @package    WebPConvert
18
 * @author     Bjørn Rosell <[email protected]>
19
 * @since      Class available since Release 2.0.0
20
 */
21
class Ewww extends AbstractConverter
22
{
23
    use CloudConverterTrait;
24
    use CurlTrait;
25
26
    protected function getUnsupportedDefaultOptions()
27
    {
28
        return [
29
            'alpha-quality',
30
            'auto-filter',
31
            'encoding',
32
            'low-memory',
33
            'use-nice'
34
        ];
35
    }
36
37 4
    protected function createOptions()
38
    {
39 4
        parent::createOptions();
40
41 4
        $this->options2->addOptions(
42 4
            new SensitiveStringOption('api-key', '')
43
        );
44 4
    }
45
46
    /**
47
     * Get api key from options or environment variable
48
     *
49
     * @return string|false  api key or false if none is set
50
     */
51 4
    private function getKey()
52
    {
53 4
        if (!empty($this->options['api-key'])) {
54 3
            return $this->options['api-key'];
55
        }
56 1
        if (defined('WEBPCONVERT_EWWW_API_KEY')) {
57
            return constant('WEBPCONVERT_EWWW_API_KEY');
58
        }
59 1
        if (!empty(getenv('WEBPCONVERT_EWWW_API_KEY'))) {
60
            return getenv('WEBPCONVERT_EWWW_API_KEY');
61
        }
62 1
        return false;
63
    }
64
65
66
    /**
67
     * Check operationality of Ewww converter.
68
     *
69
     * @throws SystemRequirementsNotMetException  if system requirements are not met (curl)
70
     * @throws ConverterNotOperationalException   if key is missing or invalid, or quota has exceeded
71
     */
72 3
    public function checkOperationality()
73
    {
74
75 3
        $apiKey = $this->getKey();
76
77 3
        if ($apiKey === false) {
78 1
            if (isset($this->options['key'])) {
79
                throw new InvalidApiKeyException(
80
                    'The "key" option has been renamed to "api-key" in webp-convert 2.0. ' .
81
                    'You must change the configuration accordingly.'
82
                );
83
            }
84
85 1
            throw new InvalidApiKeyException('Missing API key.');
86
        }
87
88 2
        if (strlen($apiKey) < 20) {
89 1
            throw new InvalidApiKeyException(
90
                'Api key is invalid. Api keys are supposed to be 32 characters long - ' .
91 1
                'the provided api key is much shorter'
92
            );
93
        }
94
95
        // Check for curl requirements
96 1
        $this->checkOperationalityForCurlTrait();
97
98 1
        $keyStatus = self::getKeyStatus($apiKey);
99
        switch ($keyStatus) {
100 1
            case 'great':
101
                break;
102 1
            case 'exceeded':
103
                throw new ConverterNotOperationalException('Quota has exceeded');
104
                break;
105 1
            case 'invalid':
106 1
                throw new InvalidApiKeyException('Api key is invalid');
107
                break;
108
        }
109
    }
110
111
    /*
112
    public function checkConvertability()
113
    {
114
        // check upload limits
115
        $this->checkConvertabilityCloudConverterTrait();
116
    }
117
    */
118
119
    // Although this method is public, do not call directly.
120
    // You should rather call the static convert() function, defined in AbstractConverter, which
121
    // takes care of preparing stuff before calling doConvert, and validating after.
122 1
    protected function doActualConvert()
123
    {
124
125 1
        $options = $this->options;
126
127 1
        $ch = self::initCurl();
128
129
        //$this->logLn('api key:' . $this->getKey());
130
131
        $postData = [
132 1
            'api_key' => $this->getKey(),
133 1
            'webp' => '1',
134 1
            'file' => curl_file_create($this->source),
135 1
            'quality' => $this->getCalculatedQuality(),
136 1
            'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
137
        ];
138
139 1
        curl_setopt_array(
140 1
            $ch,
141
            [
142 1
            CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
143 1
            CURLOPT_HTTPHEADER => [
144
                'User-Agent: WebPConvert',
145
                'Accept: image/*'
146
            ],
147 1
            CURLOPT_POSTFIELDS => $postData,
148 1
            CURLOPT_BINARYTRANSFER => true,
149 1
            CURLOPT_RETURNTRANSFER => true,
150 1
            CURLOPT_HEADER => false,
151 1
            CURLOPT_SSL_VERIFYPEER => false
152
            ]
153
        );
154
155 1
        $response = curl_exec($ch);
156
157 1
        if (curl_errno($ch)) {
158
            throw new ConversionFailedException(curl_error($ch));
159
        }
160
161
        // The API does not always return images.
162
        // For example, it may return a message such as '{"error":"invalid","t":"exceeded"}
163
        // Messages has a http content type of ie 'text/html; charset=UTF-8
164
        // Images has application/octet-stream.
165
        // So verify that we got an image back.
166 1
        if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
167
            //echo curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
168 1
            curl_close($ch);
169
170
            /* May return this: {"error":"invalid","t":"exceeded"} */
171 1
            $responseObj = json_decode($response);
172 1
            if (isset($responseObj->error)) {
173
                //echo 'error:' . $responseObj->error . '<br>';
174
                //echo $response;
175
                //self::blacklistKey($key);
176
                //throw new SystemRequirementsNotMetException('The key is invalid. Blacklisted it!');
177 1
                throw new InvalidApiKeyException('The api key is invalid');
178
            }
179
180
            throw new ConversionFailedException(
181
                'ewww api did not return an image. It could be that the key is invalid. Response: '
182
                . $response
183
            );
184
        }
185
186
        // Not sure this can happen. So just in case
187
        if ($response == '') {
188
            throw new ConversionFailedException('ewww api did not return anything');
189
        }
190
191
        $success = file_put_contents($this->destination, $response);
192
193
        if (!$success) {
194
            throw new ConversionFailedException('Error saving file');
195
        }
196
    }
197
198
    /**
199
     *  Keep subscription alive by optimizing a jpeg
200
     *  (ewww closes accounts after 6 months of inactivity - and webp conversions seems not to be counted? )
201
     */
202
    public static function keepSubscriptionAlive($source, $key)
203
    {
204
        try {
205
            $ch = curl_init();
206
        } catch (\Exception $e) {
207
            return 'curl is not installed';
208
        }
209
        if ($ch === false) {
210
            return 'curl could not be initialized';
211
        }
212
        curl_setopt_array(
213
            $ch,
214
            [
215
            CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
216
            CURLOPT_HTTPHEADER => [
217
                'User-Agent: WebPConvert',
218
                'Accept: image/*'
219
            ],
220
            CURLOPT_POSTFIELDS => [
221
                'api_key' => $key,
222
                'webp' => '0',
223
                'file' => curl_file_create($source),
224
                'domain' => $_SERVER['HTTP_HOST'],
225
                'quality' => 60,
226
                'metadata' => 0
227
            ],
228
            CURLOPT_BINARYTRANSFER => true,
229
            CURLOPT_RETURNTRANSFER => true,
230
            CURLOPT_HEADER => false,
231
            CURLOPT_SSL_VERIFYPEER => false
232
            ]
233
        );
234
235
        $response = curl_exec($ch);
236
        if (curl_errno($ch)) {
237
            return 'curl error' . curl_error($ch);
238
        }
239
        if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
240
            curl_close($ch);
241
242
            /* May return this: {"error":"invalid","t":"exceeded"} */
243
            $responseObj = json_decode($response);
244
            if (isset($responseObj->error)) {
245
                return 'The key is invalid';
246
            }
247
248
            return 'ewww api did not return an image. It could be that the key is invalid. Response: ' . $response;
249
        }
250
251
        // Not sure this can happen. So just in case
252
        if ($response == '') {
253
            return 'ewww api did not return anything';
254
        }
255
256
        return true;
257
    }
258
259
    /*
260
        public static function blacklistKey($key)
261
        {
262
        }
263
264
        public static function isKeyBlacklisted($key)
265
        {
266
        }*/
267
268
    /**
269
     *  Return "great", "exceeded" or "invalid"
270
     */
271 3
    public static function getKeyStatus($key)
272
    {
273 3
        $ch = self::initCurl();
274
275 3
        curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/verify/");
276 3
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
277 3
        curl_setopt(
278 3
            $ch,
279 3
            CURLOPT_POSTFIELDS,
280
            [
281 3
            'api_key' => $key
282
            ]
283
        );
284
285
        // The 403 forbidden is avoided with this line.
286 3
        curl_setopt(
287 3
            $ch,
288 3
            CURLOPT_USERAGENT,
289 3
            'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
290
        );
291
292 3
        $response = curl_exec($ch);
293
        // echo $response;
294 3
        if (curl_errno($ch)) {
295
            throw new \Exception(curl_error($ch));
296
        }
297 3
        curl_close($ch);
298
299
        // Possible responses:
300
        // “great” = verification successful
301
        // “exceeded” = indicates a valid key with no remaining image credits.
302
        // an empty response indicates that the key is not valid
303
304 3
        if ($response == '') {
305
            return 'invalid';
306
        }
307 3
        $responseObj = json_decode($response);
308 3
        if (isset($responseObj->error)) {
309 3
            if ($responseObj->error == 'invalid') {
310 3
                return 'invalid';
311
            } else {
312
                throw new \Exception('Ewww returned unexpected error: ' . $response);
313
            }
314
        }
315 1
        if (!isset($responseObj->status)) {
316
            throw new \Exception('Ewww returned unexpected response to verify request: ' . $response);
317
        }
318 1
        switch ($responseObj->status) {
319 1
            case 'great':
320
            case 'exceeded':
321 1
                return $responseObj->status;
322
        }
323
        throw new \Exception('Ewww returned unexpected status to verify request: "' . $responseObj->status . '"');
324
    }
325
326 1
    public static function isWorkingKey($key)
327
    {
328 1
        return (self::getKeyStatus($key) == 'great');
329
    }
330
331 1
    public static function isValidKey($key)
332
    {
333 1
        return (self::getKeyStatus($key) != 'invalid');
334
    }
335
336
    public static function getQuota($key)
337
    {
338
        $ch = self::initCurl();
339
340
        curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/quota/");
341
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
342
        curl_setopt(
343
            $ch,
344
            CURLOPT_POSTFIELDS,
345
            [
346
            'api_key' => $key
347
            ]
348
        );
349
        curl_setopt(
350
            $ch,
351
            CURLOPT_USERAGENT,
352
            'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)'
353
        );
354
355
        $response = curl_exec($ch);
356
        return $response; // ie -830 23. Seems to return empty for invalid keys
357
        // or empty
358
        //echo $response;
359
    }
360
}
361