Test Failed
Push — master ( 975525...307699 )
by Bjørn
02:39
created

Ewww::isWorkingKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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