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 -715 2.5.3 → 2.7.3 View file →
@@ -1,715 +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 - 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 - * @since 1.0.4
169 - *
170 - * @return bool
171 - */
172 - public static function IsHttps() {
173 - return ( 'https' === self::$_protocol );
174 - }
175 -
176 - /**
177 - * Sign request with the following HTTP headers:
178 - * Content-MD5: MD5(HTTP Request body)
179 - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000)
180 - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,
181 - * {scope_entity_secret_key}))
182 - *
183 - * @param string $pResourceUrl
184 - * @param array $pWPRemoteArgs
185 - *
186 - * @return array
187 - */
188 - function SignRequest( $pResourceUrl, $pWPRemoteArgs ) {
189 - $auth = $this->GenerateAuthorizationParams(
190 - $pResourceUrl,
191 - $pWPRemoteArgs['method'],
192 - ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : ''
193 - );
194 -
195 - $pWPRemoteArgs['headers']['Date'] = $auth['date'];
196 - $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization'];
197 -
198 - if ( ! empty( $auth['content_md5'] ) ) {
199 - $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5'];
200 - }
201 -
202 - return $pWPRemoteArgs;
203 - }
204 -
205 - /**
206 - * Generate Authorization request headers:
207 - *
208 - * Content-MD5: MD5(HTTP Request body)
209 - * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000)
210 - * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,
211 - * {scope_entity_secret_key}))
212 - *
213 - * @author Vova Feldman
214 - *
215 - * @param string $pResourceUrl
216 - * @param string $pMethod
217 - * @param string $pPostParams
218 - *
219 - * @return array
220 - * @throws Freemius_Exception
221 - */
222 - function GenerateAuthorizationParams(
223 - $pResourceUrl,
224 - $pMethod = 'GET',
225 - $pPostParams = ''
226 - ) {
227 - $pMethod = strtoupper( $pMethod );
228 -
229 - $eol = "\n";
230 - $content_md5 = '';
231 - $content_type = '';
232 - $now = ( time() - self::$_clock_diff );
233 - $date = date( 'r', $now );
234 -
235 - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) {
236 - $content_type = 'application/json';
237 -
238 - if ( ! empty( $pPostParams ) ) {
239 - $content_md5 = md5( $pPostParams );
240 - }
241 - }
242 -
243 - $string_to_sign = implode( $eol, array(
244 - $pMethod,
245 - $content_md5,
246 - $content_type,
247 - $date,
248 - $pResourceUrl
249 - ) );
250 -
251 - // If secret and public keys are identical, it means that
252 - // the signature uses public key hash encoding.
253 - $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP';
254 -
255 - $auth = array(
256 - 'date' => $date,
257 - 'authorization' => $auth_type . ' ' . $this->_id . ':' .
258 - $this->_public . ':' .
259 - self::Base64UrlEncode( hash_hmac(
260 - 'sha256', $string_to_sign, $this->_secret
261 - ) )
262 - );
263 -
264 - if ( ! empty( $content_md5 ) ) {
265 - $auth['content_md5'] = $content_md5;
266 - }
267 -
268 - return $auth;
269 - }
270 -
271 - /**
272 - * Get API request URL signed via query string.
273 - *
274 - * @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).
275 - *
276 - * @param string $pPath
277 - *
278 - * @throws Freemius_Exception
279 - *
280 - * @return string
281 - */
282 - function GetSignedUrl( $pPath ) {
283 - $resource = explode( '?', $this->CanonizePath( $pPath ) );
284 - $pResourceUrl = $resource[0];
285 -
286 - $auth = $this->GenerateAuthorizationParams( $pResourceUrl );
287 -
288 - return Freemius_Api_WordPress::GetUrl(
289 - $pResourceUrl . '?' .
290 - ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) .
291 - 'authorization=' . urlencode( $auth['authorization'] ) .
292 - '&auth_date=' . urlencode( $auth['date'] )
293 - , $this->_isSandbox );
294 - }
295 -
296 - /**
297 - * @author Vova Feldman
298 - *
299 - * @param string $pUrl
300 - * @param array $pWPRemoteArgs
301 - *
302 - * @return mixed
303 - */
304 - private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) {
305 - $bt = debug_backtrace();
306 -
307 - $start = microtime( true );
308 -
309 - $response = wp_remote_request( $pUrl, $pWPRemoteArgs );
310 -
311 - if ( FS_API__LOGGER_ON ) {
312 - $end = microtime( true );
313 -
314 - $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) );
315 - $is_http_error = is_wp_error( $response );
316 -
317 - self::$_logger[] = array(
318 - 'id' => count( self::$_logger ),
319 - 'start' => $start,
320 - 'end' => $end,
321 - 'total' => ( $end - $start ),
322 - 'method' => $pWPRemoteArgs['method'],
323 - 'path' => $pUrl,
324 - 'body' => $has_body ? $pWPRemoteArgs['body'] : null,
325 - 'result' => ! $is_http_error ?
326 - $response['body'] :
327 - json_encode( $response->get_error_messages() ),
328 - 'code' => ! $is_http_error ? $response['response']['code'] : null,
329 - 'backtrace' => $bt,
330 - );
331 - }
332 -
333 - return $response;
334 - }
335 -
336 - /**
337 - * @return array
338 - */
339 - static function GetLogger() {
340 - return self::$_logger;
341 - }
342 -
343 - /**
344 - * @param string $pCanonizedPath
345 - * @param string $pMethod
346 - * @param array $pParams
347 - * @param null|array $pWPRemoteArgs
348 - * @param bool $pIsSandbox
349 - * @param null|callable $pBeforeExecutionFunction
350 - *
351 - * @return object[]|object|null
352 - *
353 - * @throws \Freemius_Exception
354 - */
355 - private static function MakeStaticRequest(
356 - $pCanonizedPath,
357 - $pMethod = 'GET',
358 - $pParams = array(),
359 - $pWPRemoteArgs = null,
360 - $pIsSandbox = false,
361 - $pBeforeExecutionFunction = null
362 - ) {
363 - // Connectivity errors simulation.
364 - if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) {
365 - self::ThrowCloudFlareDDoSException();
366 - } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) {
367 - self::ThrowSquidAclException();
368 - }
369 -
370 - if ( empty( $pWPRemoteArgs ) ) {
371 - $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' .
372 - home_url();
373 -
374 - $pWPRemoteArgs = array(
375 - 'method' => strtoupper( $pMethod ),
376 - 'connect_timeout' => 10,
377 - 'timeout' => 60,
378 - 'follow_redirects' => true,
379 - 'redirection' => 5,
380 - 'user-agent' => $user_agent,
381 - 'blocking' => true,
382 - );
383 - }
384 -
385 - if ( ! isset( $pWPRemoteArgs['headers'] ) ||
386 - ! is_array( $pWPRemoteArgs['headers'] )
387 - ) {
388 - $pWPRemoteArgs['headers'] = array();
389 - }
390 -
391 - if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) {
392 - $pWPRemoteArgs['headers']['Content-type'] = 'application/json';
393 -
394 - if ( is_array( $pParams ) && 0 < count( $pParams ) ) {
395 - $pWPRemoteArgs['body'] = json_encode( $pParams );
396 - }
397 - }
398 -
399 - $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox );
400 -
401 - $resource = explode( '?', $pCanonizedPath );
402 -
403 - if ( FS_SDK__HAS_CURL ) {
404 - // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait
405 - // for 2 seconds if the server does not support this header.
406 - $pWPRemoteArgs['headers']['Expect'] = '';
407 - }
408 -
409 - if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) {
410 - $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY;
411 - }
412 -
413 - if ( false !== $pBeforeExecutionFunction &&
414 - is_callable( $pBeforeExecutionFunction )
415 - ) {
416 - $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs );
417 - }
418 -
419 - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
420 -
421 - if ( is_wp_error( $result ) ) {
422 - /**
423 - * @var WP_Error $result
424 - */
425 - if ( self::IsCurlError( $result ) ) {
426 - /**
427 - * With dual stacked DNS responses, it's possible for a server to
428 - * have IPv6 enabled but not have IPv6 connectivity. If this is
429 - * the case, cURL will try IPv4 first and if that fails, then it will
430 - * fall back to IPv6 and the error EHOSTUNREACH is returned by the
431 - * operating system.
432 - */
433 - $matches = array();
434 - $regex = '/Failed to connect to ([^:].*): Network is unreachable/';
435 - if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) {
436 - /**
437 - * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning.
438 - * @author Vova Feldman (@svovaf)
439 - */
440 - if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) {
441 - if ( strlen( inet_pton( $matches[1] ) ) === 16 ) {
442 -// error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.');
443 - // Hook to an action triggered just before cURL is executed to resolve the IP version to v4.
444 - add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 );
445 -
446 - // Re-run request.
447 - $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
448 - }
449 - }
450 - }
451 - }
452 -
453 - if ( is_wp_error( $result ) ) {
454 - self::ThrowWPRemoteException( $result );
455 - }
456 - }
457 -
458 - $response_body = $result['body'];
459 -
460 - if ( empty( $response_body ) ) {
461 - return null;
462 - }
463 -
464 - $decoded = json_decode( $response_body );
465 -
466 - if ( is_null( $decoded ) ) {
467 - if ( preg_match( '/Please turn JavaScript on/i', $response_body ) &&
468 - preg_match( '/text\/javascript/', $response_body )
469 - ) {
470 - self::ThrowCloudFlareDDoSException( $response_body );
471 - } 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 ) &&
472 - preg_match( '/squid/', $response_body )
473 - ) {
474 - self::ThrowSquidAclException( $response_body );
475 - } else {
476 - $decoded = (object) array(
477 - 'error' => (object) array(
478 - 'type' => 'Unknown',
479 - 'message' => $response_body,
480 - 'code' => 'unknown',
481 - 'http' => 402
482 - )
483 - );
484 - }
485 - }
486 -
487 - return $decoded;
488 - }
489 -
490 -
491 - /**
492 - * Makes an HTTP request. This method can be overridden by subclasses if
493 - * developers want to do fancier things or use something other than wp_remote_request()
494 - * to make the request.
495 - *
496 - * @param string $pCanonizedPath The URL to make the request to
497 - * @param string $pMethod HTTP method
498 - * @param array $pParams The parameters to use for the POST body
499 - * @param null|array $pWPRemoteArgs wp_remote_request options.
500 - *
501 - * @return object[]|object|null
502 - *
503 - * @throws Freemius_Exception
504 - */
505 - public function MakeRequest(
506 - $pCanonizedPath,
507 - $pMethod = 'GET',
508 - $pParams = array(),
509 - $pWPRemoteArgs = null
510 - ) {
511 - $resource = explode( '?', $pCanonizedPath );
512 -
513 - // Only sign request if not ping.json connectivity test.
514 - $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) );
515 -
516 - return self::MakeStaticRequest(
517 - $pCanonizedPath,
518 - $pMethod,
519 - $pParams,
520 - $pWPRemoteArgs,
521 - $this->_isSandbox,
522 - $sign_request ? array( &$this, 'SignRequest' ) : null
523 - );
524 - }
525 -
526 - /**
527 - * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter
528 - *
529 - * @param resource $handle A cURL handle returned by curl_init()
530 - *
531 - * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to
532 - * CURL_IPRESOLVE_V4
533 - *
534 - * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e
535 - */
536 - static function CurlResolveToIPv4( $handle ) {
537 - curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
538 -
539 - return $handle;
540 - }
541 -
542 - #----------------------------------------------------------------------------------
543 - #region Connectivity Test
544 - #----------------------------------------------------------------------------------
545 -
546 - /**
547 - * If successful connectivity to the API endpoint using ping.json endpoint.
548 - *
549 - * - OR -
550 - *
551 - * Validate if ping result object is valid.
552 - *
553 - * @param mixed $pPong
554 - *
555 - * @return bool
556 - */
557 - public static function Test( $pPong = null ) {
558 - $pong = is_null( $pPong ) ?
559 - self::Ping() :
560 - $pPong;
561 -
562 - return (
563 - is_object( $pong ) &&
564 - isset( $pong->api ) &&
565 - 'pong' === $pong->api
566 - );
567 - }
568 -
569 - /**
570 - * Ping API to test connectivity.
571 - *
572 - * @return object
573 - */
574 - public static function Ping() {
575 - try {
576 - $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' );
577 - } catch ( Freemius_Exception $e ) {
578 - // Map to error object.
579 - $result = (object) $e->getResult();
580 - } catch ( Exception $e ) {
581 - // Map to error object.
582 - $result = (object) array(
583 - 'error' => (object) array(
584 - 'type' => 'Unknown',
585 - 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')',
586 - 'code' => 'unknown',
587 - 'http' => 402
588 - )
589 - );
590 - }
591 -
592 - return $result;
593 - }
594 -
595 - #endregion
596 -
597 - #----------------------------------------------------------------------------------
598 - #region Connectivity Exceptions
599 - #----------------------------------------------------------------------------------
600 -
601 - /**
602 - * @param \WP_Error $pError
603 - *
604 - * @return bool
605 - */
606 - private static function IsCurlError( WP_Error $pError ) {
607 - $message = $pError->get_error_message( 'http_request_failed' );
608 -
609 - return ( 0 === strpos( $message, 'cURL' ) );
610 - }
611 -
612 - /**
613 - * @param WP_Error $pError
614 - *
615 - * @throws Freemius_Exception
616 - */
617 - private static function ThrowWPRemoteException( WP_Error $pError ) {
618 - if ( self::IsCurlError( $pError ) ) {
619 - $message = $pError->get_error_message( 'http_request_failed' );
620 -
621 - #region Check if there are any missing cURL methods.
622 -
623 - $curl_required_methods = array(
624 - 'curl_version',
625 - 'curl_exec',
626 - 'curl_init',
627 - 'curl_close',
628 - 'curl_setopt',
629 - 'curl_setopt_array',
630 - 'curl_error',
631 - );
632 -
633 - // Find all missing methods.
634 - $missing_methods = array();
635 - foreach ( $curl_required_methods as $m ) {
636 - if ( ! function_exists( $m ) ) {
637 - $missing_methods[] = $m;
638 - }
639 - }
640 -
641 - if ( ! empty( $missing_methods ) ) {
642 - throw new Freemius_Exception( array(
643 - 'error' => (object) array(
644 - 'type' => 'cUrlMissing',
645 - 'message' => $message,
646 - 'code' => 'curl_missing',
647 - 'http' => 402
648 - ),
649 - 'missing_methods' => $missing_methods,
650 - ) );
651 - }
652 -
653 - #endregion
654 -
655 - // cURL error - "cURL error {{errno}}: {{error}}".
656 - $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 );
657 -
658 - $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed';
659 - $message = ( 1 < count( $parts ) ) ? $parts[1] : $message;
660 -
661 - $e = new Freemius_Exception( array(
662 - 'error' => (object) array(
663 - 'code' => $code,
664 - 'message' => $message,
665 - 'type' => 'CurlException',
666 - ),
667 - ) );
668 - } else {
669 - $e = new Freemius_Exception( array(
670 - 'error' => (object) array(
671 - 'code' => $pError->get_error_code(),
672 - 'message' => $pError->get_error_message(),
673 - 'type' => 'WPRemoteException',
674 - ),
675 - ) );
676 - }
677 -
678 - throw $e;
679 - }
680 -
681 - /**
682 - * @param string $pResult
683 - *
684 - * @throws Freemius_Exception
685 - */
686 - private static function ThrowCloudFlareDDoSException( $pResult = '' ) {
687 - throw new Freemius_Exception( array(
688 - 'error' => (object) array(
689 - 'type' => 'CloudFlareDDoSProtection',
690 - 'message' => $pResult,
691 - 'code' => 'cloudflare_ddos_protection',
692 - 'http' => 402
693 - )
694 - ) );
695 - }
696 -
697 - /**
698 - * @param string $pResult
699 - *
700 - * @throws Freemius_Exception
701 - */
702 - private static function ThrowSquidAclException( $pResult = '' ) {
703 - throw new Freemius_Exception( array(
704 - 'error' => (object) array(
705 - 'type' => 'SquidCacheBlock',
706 - 'message' => $pResult,
707 - 'code' => 'squid_cache_block',
708 - 'http' => 402
709 - )
710 - ) );
711 - }
712 -
713 - #endregion
714 - }
715 - }
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 + }