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