Passed
Push — master ( c5de92...d797e6 )
by Bjørn
02:34
created

Wpc   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Test Coverage

Coverage 49.48%

Importance

Changes 0
Metric Value
eloc 117
dl 0
loc 228
ccs 48
cts 97
cp 0.4948
rs 10
c 0
b 0
f 0
wmc 29

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getOptionDefinitionsExtra() 0 8 1
A createRandomSaltForBlowfish() 0 14 2
A checkConvertability() 0 4 1
B checkOperationality() 0 39 7
F doActualConvert() 0 135 18
1
<?php
2
3
namespace WebPConvert\Convert\Converters;
4
5
use WebPConvert\Convert\BaseConverters\AbstractCloudCurlConverter;
6
use WebPConvert\Convert\Exceptions\ConversionFailedException;
7
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
8
use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
9
10
/**
11
 * Convert images to webp using Wpc (a cloud converter based on WebP Convert).
12
 *
13
 * @package    WebPConvert
14
 * @author     Bjørn Rosell <[email protected]>
15 4
 * @since      Class available since Release 2.0.0
16
 */
17
class Wpc extends AbstractCloudCurlConverter
18 4
{
19
    protected $processLosslessAuto = true;
20
    protected $supportsLossless = true;
21
22
    protected function getOptionDefinitionsExtra()
23
    {
24
        return [
25
            ['api-version', 'number', 0],                     /* Can currently be 0 or 1 */
26
            ['secret', 'string', 'my dog is white', true],    /* only in api v.0 */
27
            ['api-key', 'string', 'my dog is white', true],   /* new in api v.1 (renamed 'secret' to 'api-key') */
28
            ['url', 'string', '', true, true],
29
            ['crypt-api-key-in-transfer', 'boolean', false],  /* new in api v.1 */
30
        ];
31
    }
32
33
    private static function createRandomSaltForBlowfish()
34
    {
35
        $salt = '';
36
        $validCharsForSalt = array_merge(
37
            range('A', 'Z'),
38
            range('a', 'z'),
39
            range('0', '9'),
40
            ['.', '/']
41
        );
42 4
43
        for ($i=0; $i<22; $i++) {
44 4
            $salt .= $validCharsForSalt[array_rand($validCharsForSalt)];
45
        }
46 4
        return $salt;
47
    }
48 4
49
    /**
50
     * Check operationality of Wpc 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 4
     */
55 4
    public function checkOperationality()
56
    {
57 4
        // First check for curl requirements
58
        parent::checkOperationality();
59
60
        $options = $this->options;
61
62
        $apiVersion = $options['api-version'];
63
64 4
        if ($apiVersion == 0) {
65
            if (!empty($options['secret'])) {
66
                // if secret is set, we need md5() and md5_file() functions
67
                if (!function_exists('md5')) {
68
                    throw new ConverterNotOperationalException(
69
                        'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
70
                        'contents. ' .
71
                        'But the required md5() PHP function is not available.'
72
                    );
73 4
                }
74
                if (!function_exists('md5_file')) {
75
                    throw new ConverterNotOperationalException(
76
                        'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
77
                        'contents. But the required md5_file() PHP function is not available.'
78
                    );
79
                }
80 4
            }
81 1
        }
82
83 1
        if ($apiVersion == 1) {
84
        /*
85
                if (count($options['web-services']) == 0) {
86
                    throw new SystemRequirementsNotMetException('No remote host has been set up');
87
                }*/
88
        }
89
90 3
        if ($options['url'] == '') {
91
            throw new ConverterNotOperationalException(
92 3
                'Missing URL. You must install Webp Convert Cloud Service on a server, ' .
93
                'or the WebP Express plugin for Wordpress - and supply the url.'
94 3
            );
95
        }
96
    }
97
98
    /**
99 3
     * Check if specific file is convertable with current converter / converter settings.
100
     *
101
     */
102 3
    public function checkConvertability()
103 3
    {
104
        // First check for upload limits (abstract cloud converter)
105
        parent::checkConvertability();
106 3
107 3
        // TODO: some from below can be moved up here
108 3
    }
109
110
    protected function doActualConvert()
111 3
    {
112 3
        $options = $this->options;
113
114
        $apiVersion = $options['api-version'];
115 3
116
        // Got some code here:
117
        // https://coderwall.com/p/v4ps1a/send-a-file-via-post-with-curl-and-php
118
119
        $ch = self::initCurl();
120
121
        $optionsToSend = $options;
122
123
        if ($this->isQualityDetectionRequiredButFailing()) {
124
            // quality was set to "auto", but we could not meassure the quality of the jpeg locally
125
            // Ask the cloud service to do it, rather than using what we came up with.
126
            $optionsToSend['quality'] = 'auto';
127
        } else {
128
            $optionsToSend['quality'] = $this->getCalculatedQuality();
129
        }
130
131
        unset($optionsToSend['converters']);
132
        unset($optionsToSend['secret']);
133
134
        $postData = [
135
            'file' => curl_file_create($this->source),
136
            'options' => json_encode($optionsToSend),
137
            'servername' => (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '')
138
        ];
139
140
        if ($apiVersion == 0) {
141
            $postData['hash'] = md5(md5_file($this->source) . $options['secret']);
142
        }
143
144
        if ($apiVersion == 1) {
145
            $apiKey = $options['api-key'];
146
147
            if ($options['crypt-api-key-in-transfer']) {
148
                if (CRYPT_BLOWFISH == 1) {
149
                    $salt = self::createRandomSaltForBlowfish();
150
                    $postData['salt'] = $salt;
151
152 3
                    // Strip off the first 28 characters (the first 6 are always "$2y$10$". The next 22 is the salt)
153 3
                    $postData['api-key-crypted'] = substr(crypt($apiKey, '$2y$10$' . $salt . '$'), 28);
154 3
                } else {
155 3
                    if (!function_exists('crypt')) {
156 3
                        throw new ConverterNotOperationalException(
157 3
                            'Configured to crypt the api-key, but crypt() function is not available.'
158 3
                        );
159 3
                    } else {
160
                        throw new ConverterNotOperationalException(
161
                            'Configured to crypt the api-key. ' .
162 3
                            'That requires Blowfish encryption, which is not available on your current setup.'
163 3
                        );
164 1
                    }
165
                }
166
            } else {
167
                $postData['api-key'] = $apiKey;
168 2
            }
169 2
        }
170
171
        curl_setopt_array($ch, [
172
            CURLOPT_URL => $options['url'],
173
            CURLOPT_POST => 1,
174
            CURLOPT_POSTFIELDS => $postData,
175
            CURLOPT_BINARYTRANSFER => true,
176
            CURLOPT_RETURNTRANSFER => true,
177
            CURLOPT_HEADER => false,
178
            CURLOPT_SSL_VERIFYPEER => false
179 2
        ]);
180 2
181
        $response = curl_exec($ch);
182 2
        if (curl_errno($ch)) {
183 2
            throw new ConverterNotOperationalException('Curl error:' . curl_error($ch));
184 2
        }
185 2
186 2
        // Check if we got a 404
187
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
188
        if ($httpCode == 404) {
189
            curl_close($ch);
190
            throw new ConversionFailedException(
191 2
                'WPC was not found at the specified URL - we got a 404 response.'
192 2
            );
193 2
        }
194
195
        // The WPC cloud service either returns an image or an error message
196
        // Images has application/octet-stream.
197
        // Verify that we got an image back.
198
        if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
199
            curl_close($ch);
200
201
            if (substr($response, 0, 1) == '{') {
202
                $responseObj = json_decode($response, true);
203
                if (isset($responseObj['errorCode'])) {
204
                    switch ($responseObj['errorCode']) {
205
                        case 0:
206
                            throw new ConverterNotOperationalException(
207
                                'There are problems with the server setup: "' .
208
                                $responseObj['errorMessage'] . '"'
209
                            );
210
                        case 1:
211
                            throw new ConverterNotOperationalException(
212
                                'Access denied. ' . $responseObj['errorMessage']
213
                            );
214
                        default:
215
                            throw new ConversionFailedException(
216
                                'Conversion failed: "' . $responseObj['errorMessage'] . '"'
217
                            );
218
                    }
219
                }
220
            }
221
222
            // WPC 0.1 returns 'failed![error messag]' when conversion fails. Handle that.
223
            if (substr($response, 0, 7) == 'failed!') {
224
                throw new ConversionFailedException(
225
                    'WPC failed converting image: "' . substr($response, 7) . '"'
226
                );
227
            }
228
229
            if (empty($response)) {
230
                $errorMsg = 'Error: Unexpected result. We got nothing back. HTTP CODE: ' . $httpCode;
231
                throw new ConversionFailedException($errorMsg);
232
            } else {
233
                $errorMsg = 'Error: Unexpected result. We did not receive an image. We received: "';
234
                $errorMsg .= str_replace("\r", '', str_replace("\n", '', htmlentities(substr($response, 0, 400))));
235
                throw new ConversionFailedException($errorMsg . '..."');
236
            }
237
            //throw new ConverterNotOperationalException($response);
238
        }
239
240
        $success = @file_put_contents($this->destination, $response);
241
        curl_close($ch);
242
243
        if (!$success) {
244
            throw new ConversionFailedException('Error saving file. Check file permissions');
245
        }
246
        /*
247
                $curlOptions = [
248
                    'api_key' => $options['key'],
249
                    'webp' => '1',
250
                    'file' => curl_file_create($this->source),
251
                    'domain' => $_SERVER['HTTP_HOST'],
252
                    'quality' => $options['quality'],
253
                    'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
254
                ];
255
256
                curl_setopt_array($ch, [
257
                    CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
258
                    CURLOPT_HTTPHEADER => [
259
                        'User-Agent: WebPConvert',
260
                        'Accept: image/*'
261
                    ],
262
                    CURLOPT_POSTFIELDS => $curlOptions,
263
                    CURLOPT_BINARYTRANSFER => true,
264
                    CURLOPT_RETURNTRANSFER => true,
265
                    CURLOPT_HEADER => false,
266
                    CURLOPT_SSL_VERIFYPEER => false
267
                ]);*/
268
    }
269
}
270