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