Completed
Push — master ( 41c0c2...358625 )
by Bjørn
04:46 queued 11s
created

Ewww::isWorkingKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 api keys discovered during conversions (only stored during the request)  */
28
    public static $invalidApiKeysDiscoveredDuringConversion = [];
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 1
            return getenv('WEBPCONVERT_EWWW_API_KEY');
66
        }
67
        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
            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
            throw new InvalidApiKeyException('Missing API key.');
91
        }
92
93 3
        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 2
        $this->checkOperationalityForCurlTrait();
102
103 2
        if ($this->options['check-key-status-before-converting']) {
104 2
            $keyStatus = self::getKeyStatus($apiKey);
105
            switch ($keyStatus) {
106 2
                case 'great':
107 1
                    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 1
    }
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 2
    protected function doActualConvert()
130
    {
131
132 2
        $options = $this->options;
133
134 2
        $ch = self::initCurl();
135
136
        //$this->logLn('api key:' . $this->getKey());
137
138
        $postData = [
139 2
            'api_key' => $this->getKey(),
140 2
            'webp' => '1',
141 2
            'file' => curl_file_create($this->source),
142 2
            'quality' => $this->getCalculatedQuality(),
143 2
            'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
144
        ];
145
146 2
        curl_setopt_array(
147 2
            $ch,
148
            [
149 2
            CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
150 2
            CURLOPT_HTTPHEADER => [
151
                'User-Agent: WebPConvert',
152
                'Accept: image/*'
153
            ],
154 2
            CURLOPT_POSTFIELDS => $postData,
155 2
            CURLOPT_BINARYTRANSFER => true,
156 2
            CURLOPT_RETURNTRANSFER => true,
157 2
            CURLOPT_HEADER => false,
158 2
            CURLOPT_SSL_VERIFYPEER => false
159
            ]
160
        );
161
162 2
        $response = curl_exec($ch);
163
164 2
        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 2
        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
            /* May ie return this: {"error":"invalid","t":"exceeded"} */
178 1
            $responseObj = json_decode($response);
179 1
            if (isset($responseObj->error)) {
180 1
                $this->logLn('We received the following error response: ' . $responseObj->error);
181 1
                $this->logLn('Complete response: ' . json_encode($responseObj));
182
183
                // Store the invalid key in array so it can be received once the Stack is completed
184
                // (even when stack succeeds)
185 1
                if (!in_array($options['api-key'], self::$invalidApiKeysDiscoveredDuringConversion)) {
186 1
                    self::$invalidApiKeysDiscoveredDuringConversion[] = $options['api-key'];
187
                }
188 1
                throw new InvalidApiKeyException('The api key is invalid!');
189
            }
190
191
            throw new ConversionFailedException(
192
                'ewww api did not return an image. It could be that the key is invalid. Response: '
193
                . $response
194
            );
195
        }
196
197
        // Not sure this can happen. So just in case
198 1
        if ($response == '') {
199
            throw new ConversionFailedException('ewww api did not return anything');
200
        }
201
202 1
        $success = file_put_contents($this->destination, $response);
203
204 1
        if (!$success) {
205
            throw new ConversionFailedException('Error saving file');
206
        }
207 1
    }
208
209
    /**
210
     *  Keep subscription alive by optimizing a jpeg
211
     *  (ewww closes accounts after 6 months of inactivity - and webp conversions seems not to be counted? )
212
     */
213
    public static function keepSubscriptionAlive($source, $key)
214
    {
215
        try {
216
            $ch = curl_init();
217
        } catch (\Exception $e) {
218
            return 'curl is not installed';
219
        }
220
        if ($ch === false) {
221
            return 'curl could not be initialized';
222
        }
223
        curl_setopt_array(
224
            $ch,
225
            [
226
            CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
227
            CURLOPT_HTTPHEADER => [
228
                'User-Agent: WebPConvert',
229
                'Accept: image/*'
230
            ],
231
            CURLOPT_POSTFIELDS => [
232
                'api_key' => $key,
233
                'webp' => '0',
234
                'file' => curl_file_create($source),
235
                'domain' => $_SERVER['HTTP_HOST'],
236
                'quality' => 60,
237
                'metadata' => 0
238
            ],
239
            CURLOPT_BINARYTRANSFER => true,
240
            CURLOPT_RETURNTRANSFER => true,
241
            CURLOPT_HEADER => false,
242
            CURLOPT_SSL_VERIFYPEER => false
243
            ]
244
        );
245
246
        $response = curl_exec($ch);
247
        if (curl_errno($ch)) {
248
            return 'curl error' . curl_error($ch);
249
        }
250
        if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
251
            curl_close($ch);
252
253
            /* May return this: {"error":"invalid","t":"exceeded"} */
254
            $responseObj = json_decode($response);
255
            if (isset($responseObj->error)) {
256
                return 'The key is invalid';
257
            }
258
259
            return 'ewww api did not return an image. It could be that the key is invalid. Response: ' . $response;
260
        }
261
262
        // Not sure this can happen. So just in case
263
        if ($response == '') {
264
            return 'ewww api did not return anything';
265
        }
266
267
        return true;
268
    }
269
270
    /*
271
        public static function blacklistKey($key)
272
        {
273
        }
274
275
        public static function isKeyBlacklisted($key)
276
        {
277
        }*/
278
279
    /**
280
     *  Return "great", "exceeded" or "invalid"
281
     */
282 4
    public static function getKeyStatus($key)
283
    {
284 4
        $ch = self::initCurl();
285
286 4
        curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/verify/");
287 4
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
288 4
        curl_setopt($ch, CURLOPT_POSTFIELDS, [
289 4
            'api_key' => $key
290
        ]);
291
292 4
        curl_setopt($ch, CURLOPT_USERAGENT, 'WebPConvert');
293
294 4
        $response = curl_exec($ch);
295
        // echo $response;
296 4
        if (curl_errno($ch)) {
297
            throw new \Exception(curl_error($ch));
298
        }
299 4
        curl_close($ch);
300
301
        // Possible responses:
302
        // “great” = verification successful
303
        // “exceeded” = indicates a valid key with no remaining image credits.
304
        // an empty response indicates that the key is not valid
305
306 4
        if ($response == '') {
307
            return 'invalid';
308
        }
309 4
        $responseObj = json_decode($response);
310 4
        if (isset($responseObj->error)) {
311 3
            if ($responseObj->error == 'invalid') {
312 3
                return 'invalid';
313
            } else {
314
                throw new \Exception('Ewww returned unexpected error: ' . $response);
315
            }
316
        }
317 3
        if (!isset($responseObj->status)) {
318
            throw new \Exception('Ewww returned unexpected response to verify request: ' . $response);
319
        }
320 3
        switch ($responseObj->status) {
321 3
            case 'great':
322
            case 'exceeded':
323 3
                return $responseObj->status;
324
        }
325
        throw new \Exception('Ewww returned unexpected status to verify request: "' . $responseObj->status . '"');
326
    }
327
328 1
    public static function isWorkingKey($key)
329
    {
330 1
        return (self::getKeyStatus($key) == 'great');
331
    }
332
333 1
    public static function isValidKey($key)
334
    {
335 1
        return (self::getKeyStatus($key) != 'invalid');
336
    }
337
338
    public static function getQuota($key)
339
    {
340
        $ch = self::initCurl();
341
342
        curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/quota/");
343
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
344
        curl_setopt($ch, CURLOPT_POSTFIELDS, [
345
            'api_key' => $key
346
        ]);
347
        curl_setopt($ch, CURLOPT_USERAGENT, 'WebPConvert');
348
349
        $response = curl_exec($ch);
350
        return $response; // ie -830 23. Seems to return empty for invalid keys
351
        // or empty
352
        //echo $response;
353
    }
354
}
355