Passed
Push — master ( 47232b...207a99 )
by Bjørn
01:36 queued 12s
created

Ewww::checkOperationality()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 8.5969

Importance

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