PluginProbe
BlockSpare – Gutenberg Blocks, AI Content Generator & Site Builder for News, Magazine & Blogs / 2.7.3
BlockSpare – Gutenberg Blocks, AI Content Generator & Site Builder for News, Magazine & Blogs v2.7.3
5.0.1 4.1.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 2.1.2 2.5.0 2.5.3 All 39 releases
← All changes | freemius/includes/sdk/FreemiusWordPress.php +745 -704 1.2.3 → 2.7.3 View file →
@@ -1,704 +1,745 @@
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 - require_once dirname( __FILE__ ) . '/FreemiusBase.php';
19 -
20 - if ( ! defined( 'FS_SDK__USER_AGENT' ) ) {
21 - define( 'FS_SDK__USER_AGENT', 'fs-php-' . Freemius_Api_Base::VERSION );
22 - }
23 -
24 - if ( ! defined( 'FS_SDK__SIMULATE_NO_CURL' ) ) {
25 - define( 'FS_SDK__SIMULATE_NO_CURL', false );
26 - }
27 -
28 - if ( ! defined( 'FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE' ) ) {
29 - define( 'FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE', false );
30 - }
31 -
32 - if ( ! defined( 'FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL' ) ) {
33 - define( 'FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL', false );
34 - }
35 -
36 - if ( ! defined( 'FS_SDK__HAS_CURL' ) ) {
37 - if ( FS_SDK__SIMULATE_NO_CURL ) {
38 - define( 'FS_SDK__HAS_CURL', false );
39 - } else {
40 - $curl_required_methods = array(
41 - 'curl_version',
42 - 'curl_exec',
43 - 'curl_init',
44 - 'curl_close',
45 - 'curl_setopt',
46 - 'curl_setopt_array',
47 - 'curl_error',
48 - );
49 -
50 - $has_curl = true;
51 - foreach ( $curl_required_methods as $m ) {
52 - if ( ! function_exists( $m ) ) {
53 - $has_curl = false;
54 - break;
55 - }
56 - }
57 -
58 - define( 'FS_SDK__HAS_CURL', $has_curl );
59 - }
60 - }
61 -
62 - $curl_version = FS_SDK__HAS_CURL ?
63 - curl_version() :
64 - array( 'version' => '7.37' );
65 -
66 - if ( ! defined( 'FS_API__PROTOCOL' ) ) {
67 - define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' );
68 - }
69 -
70 - if ( ! defined( 'FS_API__LOGGER_ON' ) ) {
71 - define( 'FS_API__LOGGER_ON', false );
72 - }
73 -
74 - if ( ! defined( 'FS_API__ADDRESS' ) ) {
75 - define( 'FS_API__ADDRESS', '://api.freemius.com' );
76 - }
77 - if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) {
78 - define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' );
79 - }
80 -
81 - if ( class_exists( 'Freemius_Api_WordPress' ) ) {
82 - return;
83 - }
84 -
85 - class Freemius_Api_WordPress extends Freemius_Api_Base {
86 - private static $_logger = array();
87 -
88 - /**
89 - * @param string $pScope 'app', 'developer', 'user' or 'install'.
90 - * @param number $pID Element's id.
91 - * @param string $pPublic Public key.
92 - * @param string|bool $pSecret Element's secret key.
93 - * @param bool $pSandbox Whether or not to run API in sandbox mode.
94 - */
95 - public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) {
96 - // If secret key not provided, use public key encryption.
97 - if ( is_bool( $pSecret ) ) {
98 - $pSecret = $pPublic;
99 - }
100 -
101 - parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox );
102 - }
103 -
104 - public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) {
105 - $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS );
106 -
107 - if ( ':' === $address[0] ) {
108 - $address = self::$_protocol . $address;
109 - }
110 -
111 - return $address . $pCanonizedPath;
112 - }
113 -
114 - #----------------------------------------------------------------------------------
115 - #region Servers Clock Diff
116 - #----------------------------------------------------------------------------------
117 -
118 - /**
119 - * @var int Clock diff in seconds between current server to API server.
120 - */
121 - private static $_clock_diff = 0;
122 -
123 - /**
124 - * Set clock diff for all API calls.
125 - *
126 - * @since 1.0.3
127 - *
128 - * @param $pSeconds
129 - */
130 - public static function SetClockDiff( $pSeconds ) {
131 - self::$_clock_diff = $pSeconds;
132 - }
133 -
134 - /**
135 - * Find clock diff between current server to API server.
136 - *
137 - * @since 1.0.2
138 - * @return int Clock diff in seconds.
139 - */
140 - public static function FindClockDiff() {
141 - $time = time();
142 - $pong = self::Ping();
143 -
144 - return ( $time - strtotime( $pong->timestamp ) );
145 - }
146 -
147 - #endregion
148 -
149 - /**
150 - * @var string http or https
151 - */
152 - private static $_protocol = FS_API__PROTOCOL;
153 -
154 - /**
155 - * Set API connection protocol.
156 - *
157 - * @since 1.0.4
158 - */
159 - public static function SetHttp() {
160 - self::$_protocol = 'http';
161 - }
162 -
163 - /**
164 - * @since 1.0.4
165 - *
166 - * @return bool
167 - */
168 - public static function IsHttps() {
169 - return ( 'https' === self::$_protocol );
170 - }
171 -
172 - /**
173 - * Sign request with the following HTTP headers:
174 - * Content-MD5: MD5(HTTP Request body)
175 - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000)
176 - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,
177 - * {scope_entity_secret_key}))
178 - *
179 - * @param string $pResourceUrl
180 - * @param array $pWPRemoteArgs
181 - *
182 - * @return array
183 - */
184 - function SignRequest( $pResourceUrl, $pWPRemoteArgs ) {
185 - $auth = $this->GenerateAuthorizationParams(
186 - $pResourceUrl,
187 - $pWPRemoteArgs['method'],
188 - ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : ''
189 - );
190 -
191 - $pWPRemoteArgs['headers']['Date'] = $auth['date'];
192 - $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization'];
193 -
194 - if ( ! empty( $auth['content_md5'] ) ) {
195 - $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5'];
196 - }
197 -
198 - return $pWPRemoteArgs;
199 - }
200 -
201 - /**
202 - * Generate Authorization request headers:
203 - *
204 - * Content-MD5: MD5(HTTP Request body)
205 - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000)
206 - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,
207 - * {scope_entity_secret_key}))
208 - *
209 - * @author Vova Feldman
210 - *
211 - * @param string $pResourceUrl
212 - * @param string $pMethod
213 - * @param string $pPostParams
214 - *
215 - * @return array
216 - * @throws Freemius_Exception
217 - */
218 - function GenerateAuthorizationParams(
219 - $pResourceUrl,
220 - $pMethod = 'GET',
221 - $pPostParams = ''
222 - ) {
223 - $pMethod = strtoupper( $pMethod );
224 -
225 - $eol = "\n";
226 - $content_md5 = '';
227 - $content_type = '';
228 - $now = ( time() - self::$_clock_diff );
229 - $date = date( 'r', $now );
230 -
231 - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) && ! empty( $pPostParams ) ) {
232 - $content_md5 = md5( $pPostParams );
233 - $content_type = 'application/json';
234 - }
235 -
236 - $string_to_sign = implode( $eol, array(
237 - $pMethod,
238 - $content_md5,
239 - $content_type,
240 - $date,
241 - $pResourceUrl
242 - ) );
243 -
244 - // If secret and public keys are identical, it means that
245 - // the signature uses public key hash encoding.
246 - $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP';
247 -
248 - $auth = array(
249 - 'date' => $date,
250 - 'authorization' => $auth_type . ' ' . $this->_id . ':' .
251 - $this->_public . ':' .
252 - self::Base64UrlEncode( hash_hmac(
253 - 'sha256', $string_to_sign, $this->_secret
254 - ) )
255 - );
256 -
257 - if ( ! empty( $content_md5 ) ) {
258 - $auth['content_md5'] = $content_md5;
259 - }
260 -
261 - return $auth;
262 - }
263 -
264 - /**
265 - * Get API request URL signed via query string.
266 - *
267 - * @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).
268 - *
269 - * @param string $pPath
270 - *
271 - * @throws Freemius_Exception
272 - *
273 - * @return string
274 - */
275 - function GetSignedUrl( $pPath ) {
276 - $resource = explode( '?', $this->CanonizePath( $pPath ) );
277 - $pResourceUrl = $resource[0];
278 -
279 - $auth = $this->GenerateAuthorizationParams( $pResourceUrl );
280 -
281 - return Freemius_Api_WordPress::GetUrl(
282 - $pResourceUrl . '?' .
283 - ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) .
284 - 'authorization=' . urlencode( $auth['authorization'] ) .
285 - '&auth_date=' . urlencode( $auth['date'] )
286 - , $this->_isSandbox );
287 - }
288 -
289 - /**
290 - * @author Vova Feldman
291 - *
292 - * @param string $pUrl
293 - * @param array $pWPRemoteArgs
294 - *
295 - * @return mixed
296 - */
297 - private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) {
298 - $start = microtime( true );
299 -
300 - $response = wp_remote_request( $pUrl, $pWPRemoteArgs );
301 -
302 - if ( FS_API__LOGGER_ON ) {
303 - $end = microtime( true );
304 -
305 - $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) );
306 - $is_http_error = is_wp_error( $response );
307 -
308 - self::$_logger[] = array(
309 - 'id' => count( self::$_logger ),
310 - 'start' => $start,
311 - 'end' => $end,
312 - 'total' => ( $end - $start ),
313 - 'method' => $pWPRemoteArgs['method'],
314 - 'path' => $pUrl,
315 - 'body' => $has_body ? $pWPRemoteArgs['body'] : null,
316 - 'result' => ! $is_http_error ?
317 - $response['body'] :
318 - json_encode( $response->get_error_messages() ),
319 - 'code' => ! $is_http_error ? $response['response']['code'] : null,
320 - 'backtrace' => debug_backtrace(),
321 - );
322 - }
323 -
324 - return $response;
325 - }
326 -
327 - /**
328 - * @return array
329 - */
330 - static function GetLogger() {
331 - return self::$_logger;
332 - }
333 -
334 - /**
335 - * @param string $pCanonizedPath
336 - * @param string $pMethod
337 - * @param array $pParams
338 - * @param null|array $pWPRemoteArgs
339 - * @param bool $pIsSandbox
340 - * @param null|callable $pBeforeExecutionFunction
341 - *
342 - * @return object[]|object|null
343 - *
344 - * @throws \Freemius_Exception
345 - */
346 - private static function MakeStaticRequest(
347 - $pCanonizedPath,
348 - $pMethod = 'GET',
349 - $pParams = array(),
350 - $pWPRemoteArgs = null,
351 - $pIsSandbox = false,
352 - $pBeforeExecutionFunction = null
353 - ) {
354 - // Connectivity errors simulation.
355 - if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) {
356 - self::ThrowCloudFlareDDoSException();
357 - } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) {
358 - self::ThrowSquidAclException();
359 - }
360 -
361 - if ( empty( $pWPRemoteArgs ) ) {
362 - $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' .
363 - home_url();
364 -
365 - $pWPRemoteArgs = array(
366 - 'method' => strtoupper( $pMethod ),
367 - 'connect_timeout' => 10,
368 - 'timeout' => 60,
369 - 'follow_redirects' => true,
370 - 'redirection' => 5,
371 - 'user-agent' => $user_agent,
372 - 'blocking' => true,
373 - );
374 - }
375 -
376 - if ( ! isset( $pWPRemoteArgs['headers'] ) ||
377 - ! is_array( $pWPRemoteArgs['headers'] )
378 - ) {
379 - $pWPRemoteArgs['headers'] = array();
380 - }
381 -
382 - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) {
383 - if ( is_array( $pParams ) && 0 < count( $pParams ) ) {
384 - $pWPRemoteArgs['headers']['Content-type'] = 'application/json';
385 - $pWPRemoteArgs['body'] = json_encode( $pParams );
386 - }
387 - }
388 -
389 - $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox );
390 -
391 - $resource = explode( '?', $pCanonizedPath );
392 -
393 - if ( FS_SDK__HAS_CURL ) {
394 - // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait
395 - // for 2 seconds if the server does not support this header.
396 - $pWPRemoteArgs['headers']['Expect'] = '';
397 - }
398 -
399 - if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) {
400 - $pWPRemoteArgs['sslverify'] = false;
401 - }
402 -
403 - if ( false !== $pBeforeExecutionFunction &&
404 - is_callable( $pBeforeExecutionFunction )
405 - ) {
406 - $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs );
407 - }
408 -
409 - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
410 -
411 - if ( is_wp_error( $result ) ) {
412 - /**
413 - * @var WP_Error $result
414 - */
415 - if ( self::IsCurlError( $result ) ) {
416 - /**
417 - * With dual stacked DNS responses, it's possible for a server to
418 - * have IPv6 enabled but not have IPv6 connectivity. If this is
419 - * the case, cURL will try IPv4 first and if that fails, then it will
420 - * fall back to IPv6 and the error EHOSTUNREACH is returned by the
421 - * operating system.
422 - */
423 - $matches = array();
424 - $regex = '/Failed to connect to ([^:].*): Network is unreachable/';
425 - if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) {
426 - /**
427 - * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning.
428 - * @author Vova Feldman (@svovaf)
429 - */
430 - if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) {
431 - if ( strlen( inet_pton( $matches[1] ) ) === 16 ) {
432 -// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.');
433 - // Hook to an action triggered just before cURL is executed to resolve the IP version to v4.
434 - add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 );
435 -
436 - // Re-run request.
437 - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
438 - }
439 - }
440 - }
441 - }
442 -
443 - if ( is_wp_error( $result ) ) {
444 - self::ThrowWPRemoteException( $result );
445 - }
446 - }
447 -
448 - $response_body = $result['body'];
449 -
450 - if ( empty( $response_body ) ) {
451 - return null;
452 - }
453 -
454 - $decoded = json_decode( $response_body );
455 -
456 - if ( is_null( $decoded ) ) {
457 - if ( preg_match( '/Please turn JavaScript on/i', $response_body ) &&
458 - preg_match( '/text\/javascript/', $response_body )
459 - ) {
460 - self::ThrowCloudFlareDDoSException( $response_body );
461 - } else if ( 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 ) &&
462 - preg_match( '/squid/', $response_body )
463 - ) {
464 - self::ThrowSquidAclException( $response_body );
465 - } else {
466 - $decoded = (object) array(
467 - 'error' => (object) array(
468 - 'type' => 'Unknown',
469 - 'message' => $response_body,
470 - 'code' => 'unknown',
471 - 'http' => 402
472 - )
473 - );
474 - }
475 - }
476 -
477 - return $decoded;
478 - }
479 -
480 -
481 - /**
482 - * Makes an HTTP request. This method can be overridden by subclasses if
483 - * developers want to do fancier things or use something other than wp_remote_request()
484 - * to make the request.
485 - *
486 - * @param string $pCanonizedPath The URL to make the request to
487 - * @param string $pMethod HTTP method
488 - * @param array $pParams The parameters to use for the POST body
489 - * @param null|array $pWPRemoteArgs wp_remote_request options.
490 - *
491 - * @return object[]|object|null
492 - *
493 - * @throws Freemius_Exception
494 - */
495 - public function MakeRequest(
496 - $pCanonizedPath,
497 - $pMethod = 'GET',
498 - $pParams = array(),
499 - $pWPRemoteArgs = null
500 - ) {
501 - $resource = explode( '?', $pCanonizedPath );
502 -
503 - // Only sign request if not ping.json connectivity test.
504 - $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) );
505 -
506 - return self::MakeStaticRequest(
507 - $pCanonizedPath,
508 - $pMethod,
509 - $pParams,
510 - $pWPRemoteArgs,
511 - $this->_isSandbox,
512 - $sign_request ? array( &$this, 'SignRequest' ) : null
513 - );
514 - }
515 -
516 - /**
517 - * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter
518 - *
519 - * @param resource $handle A cURL handle returned by curl_init()
520 - *
521 - * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to
522 - * CURL_IPRESOLVE_V4
523 - *
524 - * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e
525 - */
526 - static function CurlResolveToIPv4( $handle ) {
527 - curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
528 -
529 - return $handle;
530 - }
531 -
532 - #----------------------------------------------------------------------------------
533 - #region Connectivity Test
534 - #----------------------------------------------------------------------------------
535 -
536 - /**
537 - * If successful connectivity to the API endpoint using ping.json endpoint.
538 - *
539 - * - OR -
540 - *
541 - * Validate if ping result object is valid.
542 - *
543 - * @param mixed $pPong
544 - *
545 - * @return bool
546 - */
547 - public static function Test( $pPong = null ) {
548 - $pong = is_null( $pPong ) ?
549 - self::Ping() :
550 - $pPong;
551 -
552 - return (
553 - is_object( $pong ) &&
554 - isset( $pong->api ) &&
555 - 'pong' === $pong->api
556 - );
557 - }
558 -
559 - /**
560 - * Ping API to test connectivity.
561 - *
562 - * @return object
563 - */
564 - public static function Ping() {
565 - try {
566 - $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' );
567 - } catch ( Freemius_Exception $e ) {
568 - // Map to error object.
569 - $result = (object) $e->getResult();
570 - } catch ( Exception $e ) {
571 - // Map to error object.
572 - $result = (object) array(
573 - 'error' => array(
574 - 'type' => 'Unknown',
575 - 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')',
576 - 'code' => 'unknown',
577 - 'http' => 402
578 - )
579 - );
580 - }
581 -
582 - return $result;
583 - }
584 -
585 - #endregion
586 -
587 - #----------------------------------------------------------------------------------
588 - #region Connectivity Exceptions
589 - #----------------------------------------------------------------------------------
590 -
591 - /**
592 - * @param \WP_Error $pError
593 - *
594 - * @return bool
595 - */
596 - private static function IsCurlError( WP_Error $pError ) {
597 - $message = $pError->get_error_message( 'http_request_failed' );
598 -
599 - return ( 0 === strpos( $message, 'cURL' ) );
600 - }
601 -
602 - /**
603 - * @param WP_Error $pError
604 - *
605 - * @throws Freemius_Exception
606 - */
607 - private static function ThrowWPRemoteException( WP_Error $pError ) {
608 - if ( self::IsCurlError( $pError ) ) {
609 - $message = $pError->get_error_message( 'http_request_failed' );
610 -
611 - #region Check if there are any missing cURL methods.
612 -
613 - $curl_required_methods = array(
614 - 'curl_version',
615 - 'curl_exec',
616 - 'curl_init',
617 - 'curl_close',
618 - 'curl_setopt',
619 - 'curl_setopt_array',
620 - 'curl_error',
621 - );
622 -
623 - // Find all missing methods.
624 - $missing_methods = array();
625 - foreach ( $curl_required_methods as $m ) {
626 - if ( ! function_exists( $m ) ) {
627 - $missing_methods[] = $m;
628 - }
629 - }
630 -
631 - if ( ! empty( $missing_methods ) ) {
632 - throw new Freemius_Exception( array(
633 - 'error' => (object) array(
634 - 'type' => 'cUrlMissing',
635 - 'message' => $message,
636 - 'code' => 'curl_missing',
637 - 'http' => 402
638 - ),
639 - 'missing_methods' => $missing_methods,
640 - ) );
641 - }
642 -
643 - #endregion
644 -
645 - // cURL error - "cURL error {{errno}}: {{error}}".
646 - $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 );
647 -
648 - $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed';
649 - $message = ( 1 < count( $parts ) ) ? $parts[1] : $message;
650 -
651 - $e = new Freemius_Exception( array(
652 - 'error' => array(
653 - 'code' => $code,
654 - 'message' => $message,
655 - 'type' => 'CurlException',
656 - ),
657 - ) );
658 - } else {
659 - $e = new Freemius_Exception( array(
660 - 'error' => array(
661 - 'code' => $pError->get_error_code(),
662 - 'message' => $pError->get_error_message(),
663 - 'type' => 'WPRemoteException',
664 - ),
665 - ) );
666 - }
667 -
668 - throw $e;
669 - }
670 -
671 - /**
672 - * @param string $pResult
673 - *
674 - * @throws Freemius_Exception
675 - */
676 - private static function ThrowCloudFlareDDoSException( $pResult = '' ) {
677 - throw new Freemius_Exception( array(
678 - 'error' => (object) array(
679 - 'type' => 'CloudFlareDDoSProtection',
680 - 'message' => $pResult,
681 - 'code' => 'cloudflare_ddos_protection',
682 - 'http' => 402
683 - )
684 - ) );
685 - }
686 -
687 - /**
688 - * @param string $pResult
689 - *
690 - * @throws Freemius_Exception
691 - */
692 - private static function ThrowSquidAclException( $pResult = '' ) {
693 - throw new Freemius_Exception( array(
694 - 'error' => (object) array(
695 - 'type' => 'SquidCacheBlock',
696 - 'message' => $pResult,
697 - 'code' => 'squid_cache_block',
698 - 'http' => 402
699 - )
700 - ) );
701 - }
702 -
703 - #endregion
704 - }
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 + if ( ! defined( 'ABSPATH' ) ) {
18 + exit;
19 + }
20 +
21 + require_once dirname( __FILE__ ) . '/FreemiusBase.php';
22 +
23 + if ( ! defined( 'FS_SDK__USER_AGENT' ) ) {
24 + define( 'FS_SDK__USER_AGENT', 'fs-php-' . Freemius_Api_Base::VERSION );
25 + }
26 +
27 + if ( ! defined( 'FS_SDK__SIMULATE_NO_CURL' ) ) {
28 + define( 'FS_SDK__SIMULATE_NO_CURL', false );
29 + }
30 +
31 + if ( ! defined( 'FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE' ) ) {
32 + define( 'FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE', false );
33 + }
34 +
35 + if ( ! defined( 'FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL' ) ) {
36 + define( 'FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL', false );
37 + }
38 +
39 + if ( ! defined( 'FS_SDK__HAS_CURL' ) ) {
40 + if ( FS_SDK__SIMULATE_NO_CURL ) {
41 + define( 'FS_SDK__HAS_CURL', false );
42 + } else {
43 + $curl_required_methods = array(
44 + 'curl_version',
45 + 'curl_exec',
46 + 'curl_init',
47 + 'curl_close',
48 + 'curl_setopt',
49 + 'curl_setopt_array',
50 + 'curl_error',
51 + );
52 +
53 + $has_curl = true;
54 + foreach ( $curl_required_methods as $m ) {
55 + if ( ! function_exists( $m ) ) {
56 + $has_curl = false;
57 + break;
58 + }
59 + }
60 +
61 + define( 'FS_SDK__HAS_CURL', $has_curl );
62 + }
63 + }
64 +
65 + if ( ! defined( 'FS_SDK__SSLVERIFY' ) ) {
66 + define( 'FS_SDK__SSLVERIFY', false );
67 + }
68 +
69 + $curl_version = FS_SDK__HAS_CURL ?
70 + curl_version() :
71 + array( 'version' => '7.37' );
72 +
73 + if ( ! defined( 'FS_API__PROTOCOL' ) ) {
74 + define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' );
75 + }
76 +
77 + if ( ! defined( 'FS_API__LOGGER_ON' ) ) {
78 + define( 'FS_API__LOGGER_ON', false );
79 + }
80 +
81 + if ( ! defined( 'FS_API__ADDRESS' ) ) {
82 + define( 'FS_API__ADDRESS', '://api.freemius.com' );
83 + }
84 + if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) {
85 + define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' );
86 + }
87 +
88 + if ( ! class_exists( 'Freemius_Api_WordPress' ) ) {
89 + class Freemius_Api_WordPress extends Freemius_Api_Base {
90 + private static $_logger = array();
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 ? FS_API__SANDBOX_ADDRESS : FS_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 + * @since 1.0.3
131 + *
132 + * @param $pSeconds
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 + * @since 1.0.2
142 + * @return int Clock diff in seconds.
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 = FS_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 + * Sets API connection protocol to HTTPS.
169 + *
170 + * @since 2.5.4
171 + */
172 + public static function SetHttps() {
173 + self::$_protocol = 'https';
174 + }
175 +
176 + /**
177 + * @since 1.0.4
178 + *
179 + * @return bool
180 + */
181 + public static function IsHttps() {
182 + return ( 'https' === self::$_protocol );
183 + }
184 +
185 + /**
186 + * Sign request with the following HTTP headers:
187 + * Content-MD5: MD5(HTTP Request body)
188 + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000)
189 + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,
190 + * {scope_entity_secret_key}))
191 + *
192 + * @param string $pResourceUrl
193 + * @param array $pWPRemoteArgs
194 + *
195 + * @return array
196 + */
197 + function SignRequest( $pResourceUrl, $pWPRemoteArgs ) {
198 + $auth = $this->GenerateAuthorizationParams(
199 + $pResourceUrl,
200 + $pWPRemoteArgs['method'],
201 + ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : ''
202 + );
203 +
204 + $pWPRemoteArgs['headers']['Date'] = $auth['date'];
205 + $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization'];
206 +
207 + if ( ! empty( $auth['content_md5'] ) ) {
208 + $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5'];
209 + }
210 +
211 + return $pWPRemoteArgs;
212 + }
213 +
214 + /**
215 + * Generate Authorization request headers:
216 + *
217 + * Content-MD5: MD5(HTTP Request body)
218 + * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000)
219 + * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,
220 + * {scope_entity_secret_key}))
221 + *
222 + * @author Vova Feldman
223 + *
224 + * @param string $pResourceUrl
225 + * @param string $pMethod
226 + * @param string $pPostParams
227 + *
228 + * @return array
229 + * @throws Freemius_Exception
230 + */
231 + function GenerateAuthorizationParams(
232 + $pResourceUrl,
233 + $pMethod = 'GET',
234 + $pPostParams = ''
235 + ) {
236 + $pMethod = strtoupper( $pMethod );
237 +
238 + $eol = "\n";
239 + $content_md5 = '';
240 + $content_type = '';
241 + $now = ( time() - self::$_clock_diff );
242 + $date = date( 'r', $now );
243 +
244 + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) {
245 + $content_type = 'application/json';
246 +
247 + if ( ! empty( $pPostParams ) ) {
248 + $content_md5 = md5( $pPostParams );
249 + }
250 + }
251 +
252 + $string_to_sign = implode( $eol, array(
253 + $pMethod,
254 + $content_md5,
255 + $content_type,
256 + $date,
257 + $pResourceUrl
258 + ) );
259 +
260 + // If secret and public keys are identical, it means that
261 + // the signature uses public key hash encoding.
262 + $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP';
263 +
264 + $auth = array(
265 + 'date' => $date,
266 + 'authorization' => $auth_type . ' ' . $this->_id . ':' .
267 + $this->_public . ':' .
268 + self::Base64UrlEncode( hash_hmac(
269 + 'sha256', $string_to_sign, $this->_secret
270 + ) )
271 + );
272 +
273 + if ( ! empty( $content_md5 ) ) {
274 + $auth['content_md5'] = $content_md5;
275 + }
276 +
277 + return $auth;
278 + }
279 +
280 + /**
281 + * Get API request URL signed via query string.
282 + *
283 + * @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).
284 + *
285 + * @param string $pPath
286 + *
287 + * @throws Freemius_Exception
288 + *
289 + * @return string
290 + */
291 + function GetSignedUrl( $pPath ) {
292 + $resource = explode( '?', $this->CanonizePath( $pPath ) );
293 + $pResourceUrl = $resource[0];
294 +
295 + $auth = $this->GenerateAuthorizationParams( $pResourceUrl );
296 +
297 + return Freemius_Api_WordPress::GetUrl(
298 + $pResourceUrl . '?' .
299 + ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) .
300 + 'authorization=' . urlencode( $auth['authorization'] ) .
301 + '&auth_date=' . urlencode( $auth['date'] )
302 + , $this->_isSandbox );
303 + }
304 +
305 + /**
306 + * @author Vova Feldman
307 + *
308 + * @param string $pUrl
309 + * @param array $pWPRemoteArgs
310 + *
311 + * @return mixed
312 + */
313 + private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) {
314 + $bt = debug_backtrace();
315 +
316 + $start = microtime( true );
317 +
318 + $response = self::RemoteRequest( $pUrl, $pWPRemoteArgs );
319 +
320 + if ( FS_API__LOGGER_ON ) {
321 + $end = microtime( true );
322 +
323 + $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) );
324 + $is_http_error = is_wp_error( $response );
325 +
326 + self::$_logger[] = array(
327 + 'id' => count( self::$_logger ),
328 + 'start' => $start,
329 + 'end' => $end,
330 + 'total' => ( $end - $start ),
331 + 'method' => $pWPRemoteArgs['method'],
332 + 'path' => $pUrl,
333 + 'body' => $has_body ? $pWPRemoteArgs['body'] : null,
334 + 'result' => ! $is_http_error ?
335 + $response['body'] :
336 + json_encode( $response->get_error_messages() ),
337 + 'code' => ! $is_http_error ? $response['response']['code'] : null,
338 + 'backtrace' => $bt,
339 + );
340 + }
341 +
342 + return $response;
343 + }
344 +
345 + /**
346 + * @author Leo Fajardo (@leorw)
347 + *
348 + * @param string $pUrl
349 + * @param array $pWPRemoteArgs
350 + *
351 + * @return array|WP_Error The response array or a WP_Error on failure.
352 + */
353 + static function RemoteRequest( $pUrl, $pWPRemoteArgs ) {
354 + $response = wp_remote_request( $pUrl, $pWPRemoteArgs );
355 +
356 + if (
357 + is_array( $response ) &&
358 + (
359 + empty( $response['headers'] ) ||
360 + empty( $response['headers']['x-api-server'] )
361 + )
362 + ) {
363 + // API is considered blocked if the response doesn't include the `x-api-server` header. When there's no error but this header doesn't exist, the response is usually not in the expected form (e.g., cannot be JSON-decoded).
364 + $response = new WP_Error( 'api_blocked', htmlentities( $response['body'] ) );
365 + }
366 +
367 + return $response;
368 + }
369 +
370 + /**
371 + * @return array
372 + */
373 + static function GetLogger() {
374 + return self::$_logger;
375 + }
376 +
377 + /**
378 + * @param string $pCanonizedPath
379 + * @param string $pMethod
380 + * @param array $pParams
381 + * @param null|array $pWPRemoteArgs
382 + * @param bool $pIsSandbox
383 + * @param null|callable $pBeforeExecutionFunction
384 + *
385 + * @return object[]|object|null
386 + *
387 + * @throws \Freemius_Exception
388 + */
389 + private static function MakeStaticRequest(
390 + $pCanonizedPath,
391 + $pMethod = 'GET',
392 + $pParams = array(),
393 + $pWPRemoteArgs = null,
394 + $pIsSandbox = false,
395 + $pBeforeExecutionFunction = null
396 + ) {
397 + // Connectivity errors simulation.
398 + if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) {
399 + self::ThrowCloudFlareDDoSException();
400 + } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) {
401 + self::ThrowSquidAclException();
402 + }
403 +
404 + if ( empty( $pWPRemoteArgs ) ) {
405 + $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' .
406 + home_url();
407 +
408 + $pWPRemoteArgs = array(
409 + 'method' => strtoupper( $pMethod ),
410 + 'connect_timeout' => 10,
411 + 'timeout' => 60,
412 + 'follow_redirects' => true,
413 + 'redirection' => 5,
414 + 'user-agent' => $user_agent,
415 + 'blocking' => true,
416 + );
417 + }
418 +
419 + if ( ! isset( $pWPRemoteArgs['headers'] ) ||
420 + ! is_array( $pWPRemoteArgs['headers'] )
421 + ) {
422 + $pWPRemoteArgs['headers'] = array();
423 + }
424 +
425 + if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) {
426 + $pWPRemoteArgs['headers']['Content-type'] = 'application/json';
427 +
428 + if ( is_array( $pParams ) && 0 < count( $pParams ) ) {
429 + $pWPRemoteArgs['body'] = json_encode( $pParams );
430 + }
431 + }
432 +
433 + $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox );
434 +
435 + $resource = explode( '?', $pCanonizedPath );
436 +
437 + if ( FS_SDK__HAS_CURL ) {
438 + // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait
439 + // for 2 seconds if the server does not support this header.
440 + $pWPRemoteArgs['headers']['Expect'] = '';
441 + }
442 +
443 + if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) {
444 + $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY;
445 + }
446 +
447 + if ( false !== $pBeforeExecutionFunction &&
448 + is_callable( $pBeforeExecutionFunction )
449 + ) {
450 + $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs );
451 + }
452 +
453 + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
454 +
455 + if ( is_wp_error( $result ) ) {
456 + /**
457 + * @var WP_Error $result
458 + */
459 + if ( self::IsCurlError( $result ) ) {
460 + /**
461 + * With dual stacked DNS responses, it's possible for a server to
462 + * have IPv6 enabled but not have IPv6 connectivity. If this is
463 + * the case, cURL will try IPv4 first and if that fails, then it will
464 + * fall back to IPv6 and the error EHOSTUNREACH is returned by the
465 + * operating system.
466 + */
467 + $matches = array();
468 + $regex = '/Failed to connect to ([^:].*): Network is unreachable/';
469 + if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) {
470 + /**
471 + * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning.
472 + * @author Vova Feldman (@svovaf)
473 + */
474 + if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) {
475 + if ( strlen( inet_pton( $matches[1] ) ) === 16 ) {
476 + /**
477 + * error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.');
478 + * Hook to an action triggered just before cURL is executed to resolve the IP version to v4.
479 + *
480 + * @phpstan-ignore-next-line
481 + */
482 + add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 );
483 +
484 + // Re-run request.
485 + $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
486 + }
487 + }
488 + }
489 + }
490 +
491 + if ( is_wp_error( $result ) ) {
492 + self::ThrowWPRemoteException( $result );
493 + }
494 + }
495 +
496 + $response_body = $result['body'];
497 +
498 + if ( empty( $response_body ) ) {
499 + return null;
500 + }
501 +
502 + $decoded = json_decode( $response_body );
503 +
504 + if ( is_null( $decoded ) ) {
505 + if ( preg_match( '/Please turn JavaScript on/i', $response_body ) &&
506 + preg_match( '/text\/javascript/', $response_body )
507 + ) {
508 + self::ThrowCloudFlareDDoSException( $response_body );
509 + } else if ( 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 ) &&
510 + preg_match( '/squid/', $response_body )
511 + ) {
512 + self::ThrowSquidAclException( $response_body );
513 + } else {
514 + $decoded = (object) array(
515 + 'error' => (object) array(
516 + 'type' => 'Unknown',
517 + 'message' => $response_body,
518 + 'code' => 'unknown',
519 + 'http' => 402
520 + )
521 + );
522 + }
523 + }
524 +
525 + return $decoded;
526 + }
527 +
528 +
529 + /**
530 + * Makes an HTTP request. This method can be overridden by subclasses if
531 + * developers want to do fancier things or use something other than wp_remote_request()
532 + * to make the request.
533 + *
534 + * @param string $pCanonizedPath The URL to make the request to
535 + * @param string $pMethod HTTP method
536 + * @param array $pParams The parameters to use for the POST body
537 + * @param null|array $pWPRemoteArgs wp_remote_request options.
538 + *
539 + * @return object[]|object|null
540 + *
541 + * @throws Freemius_Exception
542 + */
543 + public function MakeRequest(
544 + $pCanonizedPath,
545 + $pMethod = 'GET',
546 + $pParams = array(),
547 + $pWPRemoteArgs = null
548 + ) {
549 + $resource = explode( '?', $pCanonizedPath );
550 +
551 + // Only sign request if not ping.json connectivity test.
552 + $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) );
553 +
554 + return self::MakeStaticRequest(
555 + $pCanonizedPath,
556 + $pMethod,
557 + $pParams,
558 + $pWPRemoteArgs,
559 + $this->_isSandbox,
560 + $sign_request ? array( &$this, 'SignRequest' ) : null
561 + );
562 + }
563 +
564 + /**
565 + * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter
566 + *
567 + * @param resource $handle A cURL handle returned by curl_init()
568 + *
569 + * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to
570 + * CURL_IPRESOLVE_V4
571 + *
572 + * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e
573 + */
574 + static function CurlResolveToIPv4( $handle ) {
575 + curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
576 +
577 + return $handle;
578 + }
579 +
580 + #----------------------------------------------------------------------------------
581 + #region Connectivity Test
582 + #----------------------------------------------------------------------------------
583 +
584 + /**
585 + * This method exists only for backward compatibility to prevent a fatal error from happening when called from an outdated piece of code.
586 + *
587 + * @param mixed $pPong
588 + *
589 + * @return bool
590 + */
591 + public static function Test( $pPong = null ) {
592 + return (
593 + is_object( $pPong ) &&
594 + isset( $pPong->api ) &&
595 + 'pong' === $pPong->api
596 + );
597 + }
598 +
599 + /**
600 + * Ping API to test connectivity.
601 + *
602 + * @return object
603 + */
604 + public static function Ping() {
605 + try {
606 + $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' );
607 + } catch ( Freemius_Exception $e ) {
608 + // Map to error object.
609 + $result = (object) $e->getResult();
610 + } catch ( Exception $e ) {
611 + // Map to error object.
612 + $result = (object) array(
613 + 'error' => (object) array(
614 + 'type' => 'Unknown',
615 + 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')',
616 + 'code' => 'unknown',
617 + 'http' => 402
618 + )
619 + );
620 + }
621 +
622 + return $result;
623 + }
624 +
625 + #endregion
626 +
627 + #----------------------------------------------------------------------------------
628 + #region Connectivity Exceptions
629 + #----------------------------------------------------------------------------------
630 +
631 + /**
632 + * @param \WP_Error $pError
633 + *
634 + * @return bool
635 + */
636 + private static function IsCurlError( WP_Error $pError ) {
637 + $message = $pError->get_error_message( 'http_request_failed' );
638 +
639 + return ( 0 === strpos( $message, 'cURL' ) );
640 + }
641 +
642 + /**
643 + * @param WP_Error $pError
644 + *
645 + * @throws Freemius_Exception
646 + */
647 + private static function ThrowWPRemoteException( WP_Error $pError ) {
648 + if ( self::IsCurlError( $pError ) ) {
649 + $message = $pError->get_error_message( 'http_request_failed' );
650 +
651 + #region Check if there are any missing cURL methods.
652 +
653 + $curl_required_methods = array(
654 + 'curl_version',
655 + 'curl_exec',
656 + 'curl_init',
657 + 'curl_close',
658 + 'curl_setopt',
659 + 'curl_setopt_array',
660 + 'curl_error',
661 + );
662 +
663 + // Find all missing methods.
664 + $missing_methods = array();
665 + foreach ( $curl_required_methods as $m ) {
666 + if ( ! function_exists( $m ) ) {
667 + $missing_methods[] = $m;
668 + }
669 + }
670 +
671 + if ( ! empty( $missing_methods ) ) {
672 + throw new Freemius_Exception( array(
673 + 'error' => (object) array(
674 + 'type' => 'cUrlMissing',
675 + 'message' => $message,
676 + 'code' => 'curl_missing',
677 + 'http' => 402
678 + ),
679 + 'missing_methods' => $missing_methods,
680 + ) );
681 + }
682 +
683 + #endregion
684 +
685 + // cURL error - "cURL error {{errno}}: {{error}}".
686 + $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 );
687 +
688 + $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed';
689 + $message = ( 1 < count( $parts ) ) ? $parts[1] : $message;
690 +
691 + $e = new Freemius_Exception( array(
692 + 'error' => (object) array(
693 + 'code' => $code,
694 + 'message' => $message,
695 + 'type' => 'CurlException',
696 + ),
697 + ) );
698 + } else {
699 + $e = new Freemius_Exception( array(
700 + 'error' => (object) array(
701 + 'code' => $pError->get_error_code(),
702 + 'message' => $pError->get_error_message(),
703 + 'type' => 'WPRemoteException',
704 + ),
705 + ) );
706 + }
707 +
708 + throw $e;
709 + }
710 +
711 + /**
712 + * @param string $pResult
713 + *
714 + * @throws Freemius_Exception
715 + */
716 + private static function ThrowCloudFlareDDoSException( $pResult = '' ) {
717 + throw new Freemius_Exception( array(
718 + 'error' => (object) array(
719 + 'type' => 'CloudFlareDDoSProtection',
720 + 'message' => $pResult,
721 + 'code' => 'cloudflare_ddos_protection',
722 + 'http' => 402
723 + )
724 + ) );
725 + }
726 +
727 + /**
728 + * @param string $pResult
729 + *
730 + * @throws Freemius_Exception
731 + */
732 + private static function ThrowSquidAclException( $pResult = '' ) {
733 + throw new Freemius_Exception( array(
734 + 'error' => (object) array(
735 + 'type' => 'SquidCacheBlock',
736 + 'message' => $pResult,
737 + 'code' => 'squid_cache_block',
738 + 'http' => 402
739 + )
740 + ) );
741 + }
742 +
743 + #endregion
744 + }
745 + }