PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.5
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.5
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / rosell-dk / webp-convert / src / Convert / Converters / Wpc.php

Wpc.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.2.5, at vendor/rosell-dk/webp-convert/src/Convert/Converters/Wpc.php

416 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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\Converters\ConverterTraits\EncodingAutoTrait;
9 use WebPConvert\Convert\Exceptions\ConversionFailedException;
10 use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperationalException;
11 use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
12 use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\InvalidApiKeyException;
13 use WebPConvert\Options\BooleanOption;
14 use WebPConvert\Options\IntegerOption;
15 use WebPConvert\Options\SensitiveStringOption;
16 use WebPConvert\Options\OptionFactory;
17
18 /**
19 * Convert images to webp using Wpc (a cloud converter based on WebP Convert).
20 *
21 * @package WebPConvert
22 * @author Bjørn Rosell <[email protected]>
23 * @since Class available since Release 2.0.0
24 */
25 class Wpc extends AbstractConverter
26 {
27 use CloudConverterTrait;
28 use CurlTrait;
29 use EncodingAutoTrait;
30
31 protected function getUnsupportedDefaultOptions()
32 {
33 return [];
34 }
35
36 public function getUniqueOptions($imageType)
37 {
38 return OptionFactory::createOptions([
39 ['api-key', 'string', [
40 'title' => 'API key',
41 'description' => 'The API key is set up on the remote. Copy that.',
42 'default' => '',
43 'sensitive' => true,
44 'ui' => [
45 'component' => 'password',
46 'advanced' => false,
47 'display' => "option('wpc-api-version') != 0"
48 ]
49 ]],
50 ['secret', 'string', [
51 'title' => 'Secret',
52 'description' => '',
53 'default' => '',
54 'sensitive' => true,
55 'ui' => [
56 'component' => 'password',
57 'advanced' => false,
58 'display' => "option('wpc-api-version') == 0"
59 ]
60 ]],
61 ['api-url', 'string', [
62 'title' => 'API url',
63 'description' => 'The endpoint of the web service. Copy it from the remote setup',
64 'default' => '',
65 'sensitive' => true,
66 'ui' => [
67 'component' => 'password',
68 'advanced' => false,
69 ]
70 ]],
71 ['api-version', 'int', [
72 'title' => 'API version',
73 'description' =>
74 'Refers to the major version of Wpc. ' .
75 'It is probably 2, as it is a long time since 2.0 was released',
76 'default' => 2,
77 'minimum' => 0,
78 'maximum' => 2,
79 'ui' => [
80 'component' => 'select',
81 'advanced' => false,
82 'options' => [0, 1, 2],
83 ]
84 ]],
85 ['crypt-api-key-in-transfer', 'boolean', [
86 'title' => 'Crypt API key in transfer',
87 'description' =>
88 'If checked, the api key will be crypted in requests. ' .
89 'Crypting the api-key protects it from being stolen during transfer',
90 'default' => false,
91 'ui' => [
92 'component' => 'checkbox',
93 'advanced' => true,
94 'display' => "option('wpc-api-version') >= 1"
95 ]
96 ]],
97 ]);
98
99 /*return [
100 new SensitiveStringOption('api-key', ''),
101 new SensitiveStringOption('secret', ''),
102 new SensitiveStringOption('api-url', ''),
103 new SensitiveStringOption('url', ''), // DO NOT USE. Only here to keep the protection
104 new IntegerOption('api-version', 2, 0, 2),
105 new BooleanOption('crypt-api-key-in-transfer', false) // new in api v.1
106 ];*/
107 }
108
109 public function supportsLossless()
110 {
111 return ($this->options['api-version'] >= 2);
112 }
113
114 public function passOnEncodingAuto()
115 {
116 // We could make this configurable. But I guess passing it on is always to be preferred
117 // for api >= 2.
118 return ($this->options['api-version'] >= 2);
119 }
120
121 private static function createRandomSaltForBlowfish()
122 {
123 $salt = '';
124 $validCharsForSalt = array_merge(
125 range('A', 'Z'),
126 range('a', 'z'),
127 range('0', '9'),
128 ['.', '/']
129 );
130
131 for ($i = 0; $i < 22; $i++) {
132 $salt .= $validCharsForSalt[array_rand($validCharsForSalt)];
133 }
134 return $salt;
135 }
136
137 /**
138 * Get api key from options or environment variable
139 *
140 * @return string api key or empty string if none is set
141 */
142 private function getApiKey()
143 {
144 if ($this->options['api-version'] == 0) {
145 if (!empty($this->options['secret'])) {
146 return $this->options['secret'];
147 }
148 } elseif ($this->options['api-version'] >= 1) {
149 if (!empty($this->options['api-key'])) {
150 return $this->options['api-key'];
151 }
152 }
153 if (defined('WEBPCONVERT_WPC_API_KEY')) {
154 return constant('WEBPCONVERT_WPC_API_KEY');
155 }
156 if (!empty(getenv('WEBPCONVERT_WPC_API_KEY'))) {
157 return getenv('WEBPCONVERT_WPC_API_KEY');
158 }
159 return '';
160 }
161
162 /**
163 * Get url from options or environment variable
164 *
165 * @return string URL to WPC or empty string if none is set
166 */
167 private function getApiUrl()
168 {
169 if (!empty($this->options['api-url'])) {
170 return $this->options['api-url'];
171 }
172 if (defined('WEBPCONVERT_WPC_API_URL')) {
173 return constant('WEBPCONVERT_WPC_API_URL');
174 }
175 if (!empty(getenv('WEBPCONVERT_WPC_API_URL'))) {
176 return getenv('WEBPCONVERT_WPC_API_URL');
177 }
178 return '';
179 }
180
181
182 /**
183 * Check operationality of Wpc converter.
184 *
185 * @throws SystemRequirementsNotMetException if system requirements are not met (curl)
186 * @throws ConverterNotOperationalException if key is missing or invalid, or quota has exceeded
187 */
188 public function checkOperationality()
189 {
190
191 $options = $this->options;
192
193 $apiVersion = $options['api-version'];
194
195 if ($this->getApiUrl() == '') {
196 if (isset($this->options['url']) && ($this->options['url'] != '')) {
197 throw new ConverterNotOperationalException(
198 'The "url" option has been renamed to "api-url" in webp-convert 2.0. ' .
199 'You must change the configuration accordingly.'
200 );
201 }
202 throw new ConverterNotOperationalException(
203 'Missing URL. You must install Webp Convert Cloud Service on a server, ' .
204 'or the WebP Express plugin for Wordpress - and supply the url.'
205 );
206 }
207
208 if ($apiVersion == 0) {
209 if (!empty($this->getApiKey())) {
210 // if secret is set, we need md5() and md5_file() functions
211 if (!function_exists('md5')) {
212 throw new ConverterNotOperationalException(
213 'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
214 'contents. ' .
215 'But the required md5() PHP function is not available.'
216 );
217 }
218 if (!function_exists('md5_file')) {
219 throw new ConverterNotOperationalException(
220 'A secret has been set, which requires us to create a md5 hash from the secret and the file ' .
221 'contents. But the required md5_file() PHP function is not available.'
222 );
223 }
224 }
225 } else {
226 if ($options['crypt-api-key-in-transfer']) {
227 if (!function_exists('crypt')) {
228 throw new ConverterNotOperationalException(
229 'Configured to crypt the api-key, but crypt() function is not available.'
230 );
231 }
232
233 if (!defined('CRYPT_BLOWFISH')) {
234 throw new ConverterNotOperationalException(
235 'Configured to crypt the api-key. ' .
236 'That requires Blowfish encryption, which is not available on your current setup.'
237 );
238 }
239 }
240 }
241
242 // Check for curl requirements
243 $this->checkOperationalityForCurlTrait();
244 }
245
246 /*
247 public function checkConvertability()
248 {
249 // check upload limits
250 $this->checkConvertabilityCloudConverterTrait();
251
252 // TODO: some from below can be moved up here
253 }
254 */
255
256 private function createOptionsToSend()
257 {
258 $optionsToSend = $this->options;
259
260 if ($this->isQualityDetectionRequiredButFailing()) {
261 // quality was set to "auto", but we could not meassure the quality of the jpeg locally
262 // Ask the cloud service to do it, rather than using what we came up with.
263 $optionsToSend['quality'] = 'auto';
264 } else {
265 $optionsToSend['quality'] = $this->getCalculatedQuality();
266 }
267
268 // The following are unset for security reasons.
269 unset($optionsToSend['converters']);
270 unset($optionsToSend['secret']);
271 unset($optionsToSend['api-key']);
272 unset($optionsToSend['api-url']);
273
274 $apiVersion = $optionsToSend['api-version'];
275
276 if ($apiVersion == 1) {
277 // Lossless can be "auto" in api 2, but in api 1 "auto" is not supported
278 //unset($optionsToSend['lossless']);
279 } elseif ($apiVersion == 2) {
280 //unset($optionsToSend['png']);
281 //unset($optionsToSend['jpeg']);
282
283 // The following are unset for security reasons.
284 unset($optionsToSend['cwebp-command-line-options']);
285 unset($optionsToSend['command-line-options']);
286 }
287
288 return $optionsToSend;
289 }
290
291 private function createPostData()
292 {
293 $options = $this->options;
294
295 $postData = [
296 'file' => curl_file_create($this->source),
297 'options' => json_encode($this->createOptionsToSend()),
298 'servername' => (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '')
299 ];
300
301 $apiVersion = $options['api-version'];
302
303 $apiKey = $this->getApiKey();
304
305 if ($apiVersion == 0) {
306 $postData['hash'] = md5(md5_file($this->source) . $apiKey);
307 } else {
308 //$this->logLn('api key: ' . $apiKey);
309
310 if ($options['crypt-api-key-in-transfer']) {
311 $salt = self::createRandomSaltForBlowfish();
312 $postData['salt'] = $salt;
313
314 // Strip off the first 28 characters (the first 6 are always "$2y$10$". The next 22 is the salt)
315 $postData['api-key-crypted'] = substr(crypt($apiKey, '$2y$10$' . $salt . '$'), 28);
316 } else {
317 $postData['api-key'] = $apiKey;
318 }
319 }
320 return $postData;
321 }
322
323 protected function doActualConvert()
324 {
325 $ch = self::initCurl();
326
327 //$this->logLn('api url: ' . $this->getApiUrl());
328
329 curl_setopt_array($ch, [
330 CURLOPT_URL => $this->getApiUrl(),
331 CURLOPT_POST => 1,
332 CURLOPT_POSTFIELDS => $this->createPostData(),
333 CURLOPT_BINARYTRANSFER => true,
334 CURLOPT_RETURNTRANSFER => true,
335 CURLOPT_HEADER => false,
336 CURLOPT_SSL_VERIFYPEER => false
337 ]);
338
339 $response = curl_exec($ch);
340 if (curl_errno($ch)) {
341 $this->logLn('Curl error: ', 'bold');
342 $this->logLn(curl_error($ch));
343 throw new ConverterNotOperationalException('Curl error:');
344 }
345
346 // Check if we got a 404
347 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
348 if ($httpCode == 404) {
349 curl_close($ch);
350 throw new ConversionFailedException(
351 'WPC was not found at the specified URL - we got a 404 response.'
352 );
353 }
354
355 // Check for empty response
356 if (empty($response)) {
357 throw new ConversionFailedException(
358 'Error: Unexpected result. We got nothing back. ' .
359 'HTTP CODE: ' . $httpCode . '. ' .
360 'Content type:' . curl_getinfo($ch, CURLINFO_CONTENT_TYPE)
361 );
362 };
363
364 // The WPC cloud service either returns an image or an error message
365 // Images has application/octet-stream.
366 // Verify that we got an image back.
367 if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
368 curl_close($ch);
369
370 if (substr($response, 0, 1) == '{') {
371 $responseObj = json_decode($response, true);
372 if (isset($responseObj['errorCode'])) {
373 switch ($responseObj['errorCode']) {
374 case 0:
375 throw new ConverterNotOperationalException(
376 'There are problems with the server setup: "' .
377 $responseObj['errorMessage'] . '"'
378 );
379 case 1:
380 throw new InvalidApiKeyException(
381 'Access denied. ' . $responseObj['errorMessage']
382 );
383 default:
384 throw new ConversionFailedException(
385 'Conversion failed: "' . $responseObj['errorMessage'] . '"'
386 );
387 }
388 }
389 }
390
391 // WPC 0.1 returns 'failed![error messag]' when conversion fails. Handle that.
392 if (substr($response, 0, 7) == 'failed!') {
393 throw new ConversionFailedException(
394 'WPC failed converting image: "' . substr($response, 7) . '"'
395 );
396 }
397
398 $this->logLn('Bummer, we did not receive an image');
399 $this->log('What we received starts with: "');
400 $this->logLn(
401 str_replace("\r", '', str_replace("\n", '', htmlentities(substr($response, 0, 400)))) . '..."'
402 );
403
404 throw new ConversionFailedException('Unexpected result. We did not receive an image but something else.');
405 //throw new ConverterNotOperationalException($response);
406 }
407
408 $success = file_put_contents($this->destination, $response);
409 curl_close($ch);
410
411 if (!$success) {
412 throw new ConversionFailedException('Error saving file. Check file permissions');
413 }
414 }
415 }
416