PluginProbe ʕ •ᴥ•ʔ
Robin Image Optimizer – Unlimited Image Optimization, WebP & AVIF / trunk
Robin Image Optimizer – Unlimited Image Optimization, WebP & AVIF vtrunk
2.0.5 trunk 1.3.7 1.4.0 1.4.1 1.4.2 1.4.6 1.5.0 1.5.3 1.5.6 1.5.8 1.6.5 1.6.6 1.6.9 1.7.0 1.7.4 1.8.1 1.8.2 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4
robin-image-optimizer / libs / factory / freemius / includes / sdk / FreemiusWordPress.php
robin-image-optimizer / libs / factory / freemius / includes / sdk Last commit date
Exceptions 5 months ago FreemiusBase.php 5 months ago FreemiusWordPress.php 5 months ago LICENSE.txt 3 years ago index.php 6 months ago
FreemiusWordPress.php
672 lines
1 <?php
2 /**
3 * Copyright 2016 Freemius, Inc.
4 *
5 * Licensed under the GPL v2 (the "License"); you may
6 * not use this file except in compliance with the License. You may obtain
7 * a copy of the License at
8 *
9 * http://choosealicense.com/licenses/gpl-v2/
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations
15 * under the License.
16 */
17
18 namespace WBCR\Factory_Freemius_Rio_600\Sdk;
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit;
22 }
23
24 require_once __DIR__ . '/FreemiusBase.php';
25
26 if ( ! defined( 'FREEMIUS_SDK__USER_AGENT' ) ) {
27 define( 'FREEMIUS_SDK__USER_AGENT', 'fs-php-' . Freemius_Api_Base::VERSION );
28 }
29
30 if ( ! defined( 'FREEMIUS_SDK__SIMULATE_NO_CURL' ) ) {
31 define( 'FREEMIUS_SDK__SIMULATE_NO_CURL', false );
32 }
33
34 if ( ! defined( 'FREEMIUS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE' ) ) {
35 define( 'FREEMIUS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE', false );
36 }
37
38 if ( ! defined( 'FREEMIUS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL' ) ) {
39 define( 'FREEMIUS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL', false );
40 }
41
42 if ( ! defined( 'FREEMIUS_SDK__HAS_CURL' ) ) {
43 if ( FREEMIUS_SDK__SIMULATE_NO_CURL ) {
44 define( 'FREEMIUS_SDK__HAS_CURL', false );
45 } else {
46 $curl_required_methods = [
47 'curl_version',
48 'curl_exec',
49 'curl_init',
50 'curl_close',
51 'curl_setopt',
52 'curl_setopt_array',
53 'curl_error',
54 ];
55
56 $has_curl = true;
57 foreach ( $curl_required_methods as $m ) {
58 if ( ! function_exists( $m ) ) {
59 $has_curl = false;
60 break;
61 }
62 }
63
64 define( 'FREEMIUS_SDK__HAS_CURL', $has_curl );
65 }
66 }
67 $curl_version = FREEMIUS_SDK__HAS_CURL ? curl_version() : [ 'version' => '7.37' ];
68
69 if ( ! defined( 'FREEMIUS_API__PROTOCOL' ) ) {
70 define( 'FREEMIUS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' );
71 }
72
73 if ( ! defined( 'FREEMIUS_API__LOGGER_ON' ) ) {
74 define( 'FREEMIUS_API__LOGGER_ON', false );
75 }
76
77 if ( ! defined( 'FREEMIUS_API__ADDRESS' ) ) {
78 define( 'FREEMIUS_API__ADDRESS', '://api.freemius.com' );
79 }
80 if ( ! defined( 'FREEMIUS_API__SANDBOX_ADDRESS' ) ) {
81 define( 'FREEMIUS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' );
82 }
83
84 if ( class_exists( 'WBCR\Factory_Freemius_Rio_600\Sdk\Freemius_Api_WordPress' ) ) {
85 return;
86 }
87
88 class Freemius_Api_WordPress extends Freemius_Api_Base {
89
90 private static $_logger = [];
91
92 /**
93 * @param string $pScope 'app', 'developer', 'user' or 'install'.
94 * @param number $pID Element's id.
95 * @param string $pPublic Public key.
96 * @param string|bool $pSecret Element's secret key.
97 * @param bool $pSandbox Whether or not to run API in sandbox mode.
98 */
99 public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) {
100 // If secret key not provided, use public key encryption.
101 if ( is_bool( $pSecret ) ) {
102 $pSecret = $pPublic;
103 }
104
105 parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox );
106 }
107
108 public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) {
109 $address = ( $pIsSandbox ? FREEMIUS_API__SANDBOX_ADDRESS : FREEMIUS_API__ADDRESS );
110
111 if ( ':' === $address[0] ) {
112 $address = self::$_protocol . $address;
113 }
114
115 return $address . $pCanonizedPath;
116 }
117
118 // ----------------------------------------------------------------------------------
119 // region Servers Clock Diff
120 // ----------------------------------------------------------------------------------
121
122 /**
123 * @var int Clock diff in seconds between current server to API server.
124 */
125 private static $_clock_diff = 0;
126
127 /**
128 * Set clock diff for all API calls.
129 *
130 * @param $pSeconds
131 *
132 * @since 1.0.3
133 */
134 public static function SetClockDiff( $pSeconds ) {
135 self::$_clock_diff = $pSeconds;
136 }
137
138 /**
139 * Find clock diff between current server to API server.
140 *
141 * @return int Clock diff in seconds.
142 * @since 1.0.2
143 */
144 public static function FindClockDiff() {
145 $time = time();
146 $pong = self::Ping();
147
148 return ( $time - strtotime( $pong->timestamp ) );
149 }
150
151 // endregion
152
153 /**
154 * @var string http or https
155 */
156 private static $_protocol = FREEMIUS_API__PROTOCOL;
157
158 /**
159 * Set API connection protocol.
160 *
161 * @since 1.0.4
162 */
163 public static function SetHttp() {
164 self::$_protocol = 'http';
165 }
166
167 /**
168 * @return bool
169 * @since 1.0.4
170 */
171 public static function IsHttps() {
172 return ( 'https' === self::$_protocol );
173 }
174
175 /**
176 * Sign request with the following HTTP headers:
177 * Content-MD5: MD5(HTTP Request body)
178 * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000)
179 * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,
180 * {scope_entity_secret_key}))
181 *
182 * @param string $pResourceUrl
183 * @param array $pWPRemoteArgs
184 *
185 * @return array
186 */
187 function SignRequest( $pResourceUrl, $pWPRemoteArgs ) {
188 $auth = $this->GenerateAuthorizationParams( $pResourceUrl, $pWPRemoteArgs['method'], ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' );
189
190 $pWPRemoteArgs['headers']['Date'] = $auth['date'];
191 $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization'];
192
193 if ( ! empty( $auth['content_md5'] ) ) {
194 $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5'];
195 }
196
197 return $pWPRemoteArgs;
198 }
199
200 /**
201 * Generate Authorization request headers:
202 *
203 * Content-MD5: MD5(HTTP Request body)
204 * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000)
205 * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,
206 * {scope_entity_secret_key}))
207 *
208 * @param string $pResourceUrl
209 * @param string $pMethod
210 * @param string $pPostParams
211 *
212 * @return array
213 * @throws Freemius_Exception
214 */
215 function GenerateAuthorizationParams( $pResourceUrl, $pMethod = 'GET', $pPostParams = '' ) {
216 $pMethod = strtoupper( $pMethod );
217
218 $eol = "\n";
219 $content_md5 = '';
220 $content_type = '';
221 $now = ( time() - self::$_clock_diff );
222 $date = date( 'r', $now );
223
224 if ( in_array( $pMethod, [ 'POST', 'PUT' ] ) && ! empty( $pPostParams ) ) {
225 $content_md5 = md5( $pPostParams );
226 $content_type = 'application/json';
227 }
228
229 $string_to_sign = implode(
230 $eol,
231 [
232 $pMethod,
233 $content_md5,
234 $content_type,
235 $date,
236 $pResourceUrl,
237 ]
238 );
239
240 // If secret and public keys are identical, it means that
241 // the signature uses public key hash encoding.
242 $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP';
243
244 $auth = [
245 'date' => $date,
246 'authorization' => $auth_type . ' ' . $this->_id . ':' . $this->_public . ':' . self::Base64UrlEncode( hash_hmac( 'sha256', $string_to_sign, $this->_secret ) ),
247 ];
248
249 if ( ! empty( $content_md5 ) ) {
250 $auth['content_md5'] = $content_md5;
251 }
252
253 return $auth;
254 }
255
256 /**
257 * Get API request URL signed via query string.
258 *
259 * @param string $pPath
260 *
261 * @return string
262 * @throws Freemius_Exception
263 *
264 * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&amp;` which breaks the URL (Added by @svovaf).
265 */
266 function GetSignedUrl( $pPath ) {
267 $resource = explode( '?', $this->CanonizePath( $pPath ) );
268 $pResourceUrl = $resource[0];
269
270 $auth = $this->GenerateAuthorizationParams( $pResourceUrl );
271
272 return self::GetUrl( $pResourceUrl . '?' . ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . 'authorization=' . urlencode( $auth['authorization'] ) . '&auth_date=' . urlencode( $auth['date'] ), $this->_isSandbox );
273 }
274
275 /**
276 * @param string $pUrl
277 * @param array $pWPRemoteArgs
278 *
279 * @return mixed
280 */
281 private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) {
282 $start = microtime( true );
283
284 $response = wp_remote_request( $pUrl, $pWPRemoteArgs );
285
286 if ( FREEMIUS_API__LOGGER_ON ) {
287 $end = microtime( true );
288
289 $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) );
290 $is_http_error = is_wp_error( $response );
291
292 self::$_logger[] = [
293 'id' => count( self::$_logger ),
294 'start' => $start,
295 'end' => $end,
296 'total' => ( $end - $start ),
297 'method' => $pWPRemoteArgs['method'],
298 'path' => $pUrl,
299 'body' => $has_body ? $pWPRemoteArgs['body'] : null,
300 'result' => ! $is_http_error ? $response['body'] : json_encode( $response->get_error_messages() ),
301 'code' => ! $is_http_error ? $response['response']['code'] : null,
302 'backtrace' => debug_backtrace(),
303 ];
304 }
305
306 return $response;
307 }
308
309 /**
310 * @return array
311 */
312 static function GetLogger() {
313 return self::$_logger;
314 }
315
316 /**
317 * @param string $pCanonizedPath
318 * @param string $pMethod
319 * @param array $pParams
320 * @param null|array $pWPRemoteArgs
321 * @param bool $pIsSandbox
322 * @param null|callable $pBeforeExecutionFunction
323 *
324 * @return object[]|object|null
325 *
326 * @throws \Freemius_Exception
327 */
328 private static function MakeStaticRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = [], $pWPRemoteArgs = null, $pIsSandbox = false, $pBeforeExecutionFunction = null ) {
329 // Connectivity errors simulation.
330 if ( FREEMIUS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) {
331 self::ThrowCloudFlareDDoSException();
332 } elseif ( FREEMIUS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) {
333 self::ThrowSquidAclException();
334 }
335
336 if ( empty( $pWPRemoteArgs ) ) {
337 $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . home_url();
338
339 $pWPRemoteArgs = [
340 'method' => strtoupper( $pMethod ),
341 'connect_timeout' => 10,
342 'timeout' => 60,
343 'follow_redirects' => true,
344 'redirection' => 5,
345 'user-agent' => $user_agent,
346 'blocking' => true,
347 ];
348 }
349
350 if ( ! isset( $pWPRemoteArgs['headers'] ) || ! is_array( $pWPRemoteArgs['headers'] ) ) {
351 $pWPRemoteArgs['headers'] = [];
352 }
353
354 if ( in_array( $pMethod, [ 'POST', 'PUT' ] ) ) {
355 if ( is_array( $pParams ) && 0 < count( $pParams ) ) {
356 $pWPRemoteArgs['headers']['Content-type'] = 'application/json';
357 $pWPRemoteArgs['body'] = json_encode( $pParams );
358 }
359 }
360
361 $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox );
362
363 $resource = explode( '?', $pCanonizedPath );
364
365 if ( FREEMIUS_SDK__HAS_CURL ) {
366 // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait
367 // for 2 seconds if the server does not support this header.
368 $pWPRemoteArgs['headers']['Expect'] = '';
369 }
370
371 if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) {
372 $pWPRemoteArgs['sslverify'] = false;
373 }
374
375 if ( false !== $pBeforeExecutionFunction && is_callable( $pBeforeExecutionFunction ) ) {
376 $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs );
377 }
378
379 $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
380
381 if ( is_wp_error( $result ) ) {
382 /**
383 * @var \WP_Error $result
384 */
385 if ( self::IsCurlError( $result ) ) {
386 /**
387 * With dual stacked DNS responses, it's possible for a server to
388 * have IPv6 enabled but not have IPv6 connectivity. If this is
389 * the case, cURL will try IPv4 first and if that fails, then it will
390 * fall back to IPv6 and the error EHOSTUNREACH is returned by the
391 * operating system.
392 */
393 $matches = [];
394 $regex = '/Failed to connect to ([^:].*): Network is unreachable/';
395 if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) {
396 /**
397 * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning.
398 */
399 if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) {
400 if ( strlen( inet_pton( $matches[1] ) ) === 16 ) {
401 // error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.');
402 // Hook to an action triggered just before cURL is executed to resolve the IP version to v4.
403 add_action( 'http_api_curl', 'WBCR\Factory_Freemius_Rio_600\Sdk\Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 );
404
405 // Re-run request.
406 $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
407 }
408 }
409 }
410 }
411
412 if ( is_wp_error( $result ) ) {
413 self::ThrowWPRemoteException( $result );
414 }
415 }
416
417 $response_body = $result['body'];
418
419 if ( empty( $response_body ) ) {
420 return null;
421 }
422
423 $decoded = json_decode( $response_body );
424
425 if ( is_null( $decoded ) ) {
426 if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && preg_match( '/text\/javascript/', $response_body ) ) {
427 self::ThrowCloudFlareDDoSException( $response_body );
428 } elseif ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && preg_match( '/squid/', $response_body ) ) {
429 self::ThrowSquidAclException( $response_body );
430 } else {
431 $decoded = (object) [
432 'error' => (object) [
433 'type' => 'Unknown',
434 'message' => $response_body,
435 'code' => 'unknown',
436 'http' => 402,
437 ],
438 ];
439 }
440 }
441
442 return $decoded;
443 }
444
445
446 /**
447 * Makes an HTTP request. This method can be overridden by subclasses if
448 * developers want to do fancier things or use something other than wp_remote_request()
449 * to make the request.
450 *
451 * @param string $pCanonizedPath The URL to make the request to
452 * @param string $pMethod HTTP method
453 * @param array $pParams The parameters to use for the POST body
454 * @param null|array $pWPRemoteArgs wp_remote_request options.
455 *
456 * @return object[]|object|null
457 *
458 * @throws Freemius_Exception
459 */
460 public function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = [], $pWPRemoteArgs = null ) {
461 $resource = explode( '?', $pCanonizedPath );
462
463 // Only sign request if not ping.json connectivity test.
464 $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], -strlen( '/v1/ping.json' ) ) ) );
465
466 return self::MakeStaticRequest(
467 $pCanonizedPath,
468 $pMethod,
469 $pParams,
470 $pWPRemoteArgs,
471 $this->_isSandbox,
472 $sign_request ? [
473 &$this,
474 'SignRequest',
475 ] : null
476 );
477 }
478
479 /**
480 * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter
481 *
482 * @param resource $handle A cURL handle returned by curl_init()
483 *
484 * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to
485 * CURL_IPRESOLVE_V4
486 *
487 * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e
488 */
489 static function CurlResolveToIPv4( $handle ) {
490 curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
491
492 return $handle;
493 }
494
495 // ----------------------------------------------------------------------------------
496 // region Connectivity Test
497 // ----------------------------------------------------------------------------------
498
499 /**
500 * If successful connectivity to the API endpoint using ping.json endpoint.
501 *
502 * - OR -
503 *
504 * Validate if ping result object is valid.
505 *
506 * @param mixed $pPong
507 *
508 * @return bool
509 */
510 public static function Test( $pPong = null ) {
511 $pong = is_null( $pPong ) ? self::Ping() : $pPong;
512
513 return ( is_object( $pong ) && isset( $pong->api ) && 'pong' === $pong->api );
514 }
515
516 /**
517 * Ping API to test connectivity.
518 *
519 * @return object
520 */
521 public static function Ping() {
522 try {
523 $result = self::MakeStaticRequest( '/v' . FREEMIUS_API__VERSION . '/ping.json' );
524 } catch ( Freemius_Exception $e ) {
525 // Map to error object.
526 $result = (object) $e->getResult();
527 } catch ( \Exception $e ) {
528 // Map to error object.
529 $result = (object) [
530 'error' => [
531 'type' => 'Unknown',
532 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')',
533 'code' => 'unknown',
534 'http' => 402,
535 ],
536 ];
537 }
538
539 return $result;
540 }
541
542 // endregion
543
544 // ----------------------------------------------------------------------------------
545 // region Connectivity Exceptions
546 // ----------------------------------------------------------------------------------
547
548 /**
549 * @param \WP_Error $pError
550 *
551 * @return bool
552 */
553 private static function IsCurlError( \WP_Error $pError ) {
554 $message = $pError->get_error_message( 'http_request_failed' );
555
556 return ( 0 === strpos( $message, 'cURL' ) );
557 }
558
559 /**
560 * @param \WP_Error $pError
561 *
562 * @throws Freemius_Exception
563 */
564 private static function ThrowWPRemoteException( \WP_Error $pError ) {
565 if ( self::IsCurlError( $pError ) ) {
566 $message = $pError->get_error_message( 'http_request_failed' );
567
568 // region Check if there are any missing cURL methods.
569
570 $curl_required_methods = [
571 'curl_version',
572 'curl_exec',
573 'curl_init',
574 'curl_close',
575 'curl_setopt',
576 'curl_setopt_array',
577 'curl_error',
578 ];
579
580 // Find all missing methods.
581 $missing_methods = [];
582 foreach ( $curl_required_methods as $m ) {
583 if ( ! function_exists( $m ) ) {
584 $missing_methods[] = $m;
585 }
586 }
587
588 if ( ! empty( $missing_methods ) ) {
589 throw new Freemius_Exception(
590 [
591 'error' => (object) [
592 'type' => 'cUrlMissing',
593 'message' => $message,
594 'code' => 'curl_missing',
595 'http' => 402,
596 ],
597 'missing_methods' => $missing_methods,
598 ]
599 );
600 }
601
602 // endregion
603
604 // cURL error - "cURL error {{errno}}: {{error}}".
605 $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 );
606
607 $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed';
608 $message = ( 1 < count( $parts ) ) ? $parts[1] : $message;
609
610 $e = new Freemius_Exception(
611 [
612 'error' => [
613 'code' => $code,
614 'message' => $message,
615 'type' => 'CurlException',
616 ],
617 ]
618 );
619 } else {
620 $e = new Freemius_Exception(
621 [
622 'error' => [
623 'code' => $pError->get_error_code(),
624 'message' => $pError->get_error_message(),
625 'type' => 'WPRemoteException',
626 ],
627 ]
628 );
629 }
630
631 throw $e;
632 }
633
634 /**
635 * @param string $pResult
636 *
637 * @throws Freemius_Exception
638 */
639 private static function ThrowCloudFlareDDoSException( $pResult = '' ) {
640 throw new Freemius_Exception(
641 [
642 'error' => (object) [
643 'type' => 'CloudFlareDDoSProtection',
644 'message' => $pResult,
645 'code' => 'cloudflare_ddos_protection',
646 'http' => 402,
647 ],
648 ]
649 );
650 }
651
652 /**
653 * @param string $pResult
654 *
655 * @throws Freemius_Exception
656 */
657 private static function ThrowSquidAclException( $pResult = '' ) {
658 throw new Freemius_Exception(
659 [
660 'error' => (object) [
661 'type' => 'SquidCacheBlock',
662 'message' => $pResult,
663 'code' => 'squid_cache_block',
664 'http' => 402,
665 ],
666 ]
667 );
668 }
669
670 // endregion
671 }
672