PluginProbe ʕ •ᴥ•ʔ
Code Manager / 1.0.13
Code Manager v1.0.13
1.0.48 1.0.47 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.3 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 1.0.39 1.0.4 1.0.40 1.0.41 1.0.42 1.0.43 1.0.44 1.0.45 1.0.46 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9
code-manager / freemius / includes / sdk / FreemiusWordPress.php
code-manager / freemius / includes / sdk Last commit date
Exceptions 4 years ago FreemiusBase.php 4 years ago FreemiusWordPress.php 4 years ago LICENSE.txt 4 years ago index.php 4 years ago
FreemiusWordPress.php
712 lines
1 <?php
2 /**
3 * Copyright 2016 Freemius, Inc.
4 *
5 * Licensed under the GPL v2 (the "License"); you may
6 * not use this file except in compliance with the License. You may obtain
7 * a copy of the License at
8 *
9 * http://choosealicense.com/licenses/gpl-v2/
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations
15 * under the License.
16 */
17
18 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 if ( ! defined( 'FS_SDK__SSLVERIFY' ) ) {
63 define( 'FS_SDK__SSLVERIFY', false );
64 }
65
66 $curl_version = FS_SDK__HAS_CURL ?
67 curl_version() :
68 array( 'version' => '7.37' );
69
70 if ( ! defined( 'FS_API__PROTOCOL' ) ) {
71 define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' );
72 }
73
74 if ( ! defined( 'FS_API__LOGGER_ON' ) ) {
75 define( 'FS_API__LOGGER_ON', false );
76 }
77
78 if ( ! defined( 'FS_API__ADDRESS' ) ) {
79 define( 'FS_API__ADDRESS', '://api.freemius.com' );
80 }
81 if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) {
82 define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' );
83 }
84
85 if ( class_exists( 'Freemius_Api_WordPress' ) ) {
86 return;
87 }
88
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 $start = microtime( true );
306
307 $response = wp_remote_request( $pUrl, $pWPRemoteArgs );
308
309 if ( FS_API__LOGGER_ON ) {
310 $end = microtime( true );
311
312 $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) );
313 $is_http_error = is_wp_error( $response );
314
315 self::$_logger[] = array(
316 'id' => count( self::$_logger ),
317 'start' => $start,
318 'end' => $end,
319 'total' => ( $end - $start ),
320 'method' => $pWPRemoteArgs['method'],
321 'path' => $pUrl,
322 'body' => $has_body ? $pWPRemoteArgs['body'] : null,
323 'result' => ! $is_http_error ?
324 $response['body'] :
325 json_encode( $response->get_error_messages() ),
326 'code' => ! $is_http_error ? $response['response']['code'] : null,
327 'backtrace' => debug_backtrace(),
328 );
329 }
330
331 return $response;
332 }
333
334 /**
335 * @return array
336 */
337 static function GetLogger() {
338 return self::$_logger;
339 }
340
341 /**
342 * @param string $pCanonizedPath
343 * @param string $pMethod
344 * @param array $pParams
345 * @param null|array $pWPRemoteArgs
346 * @param bool $pIsSandbox
347 * @param null|callable $pBeforeExecutionFunction
348 *
349 * @return object[]|object|null
350 *
351 * @throws \Freemius_Exception
352 */
353 private static function MakeStaticRequest(
354 $pCanonizedPath,
355 $pMethod = 'GET',
356 $pParams = array(),
357 $pWPRemoteArgs = null,
358 $pIsSandbox = false,
359 $pBeforeExecutionFunction = null
360 ) {
361 // Connectivity errors simulation.
362 if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) {
363 self::ThrowCloudFlareDDoSException();
364 } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) {
365 self::ThrowSquidAclException();
366 }
367
368 if ( empty( $pWPRemoteArgs ) ) {
369 $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' .
370 home_url();
371
372 $pWPRemoteArgs = array(
373 'method' => strtoupper( $pMethod ),
374 'connect_timeout' => 10,
375 'timeout' => 60,
376 'follow_redirects' => true,
377 'redirection' => 5,
378 'user-agent' => $user_agent,
379 'blocking' => true,
380 );
381 }
382
383 if ( ! isset( $pWPRemoteArgs['headers'] ) ||
384 ! is_array( $pWPRemoteArgs['headers'] )
385 ) {
386 $pWPRemoteArgs['headers'] = array();
387 }
388
389 if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) {
390 $pWPRemoteArgs['headers']['Content-type'] = 'application/json';
391
392 if ( is_array( $pParams ) && 0 < count( $pParams ) ) {
393 $pWPRemoteArgs['body'] = json_encode( $pParams );
394 }
395 }
396
397 $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox );
398
399 $resource = explode( '?', $pCanonizedPath );
400
401 if ( FS_SDK__HAS_CURL ) {
402 // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait
403 // for 2 seconds if the server does not support this header.
404 $pWPRemoteArgs['headers']['Expect'] = '';
405 }
406
407 if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) {
408 $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY;
409 }
410
411 if ( false !== $pBeforeExecutionFunction &&
412 is_callable( $pBeforeExecutionFunction )
413 ) {
414 $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs );
415 }
416
417 $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
418
419 if ( is_wp_error( $result ) ) {
420 /**
421 * @var WP_Error $result
422 */
423 if ( self::IsCurlError( $result ) ) {
424 /**
425 * With dual stacked DNS responses, it's possible for a server to
426 * have IPv6 enabled but not have IPv6 connectivity. If this is
427 * the case, cURL will try IPv4 first and if that fails, then it will
428 * fall back to IPv6 and the error EHOSTUNREACH is returned by the
429 * operating system.
430 */
431 $matches = array();
432 $regex = '/Failed to connect to ([^:].*): Network is unreachable/';
433 if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) {
434 /**
435 * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning.
436 * @author Vova Feldman (@svovaf)
437 */
438 if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) {
439 if ( strlen( inet_pton( $matches[1] ) ) === 16 ) {
440 // error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.');
441 // Hook to an action triggered just before cURL is executed to resolve the IP version to v4.
442 add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 );
443
444 // Re-run request.
445 $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs );
446 }
447 }
448 }
449 }
450
451 if ( is_wp_error( $result ) ) {
452 self::ThrowWPRemoteException( $result );
453 }
454 }
455
456 $response_body = $result['body'];
457
458 if ( empty( $response_body ) ) {
459 return null;
460 }
461
462 $decoded = json_decode( $response_body );
463
464 if ( is_null( $decoded ) ) {
465 if ( preg_match( '/Please turn JavaScript on/i', $response_body ) &&
466 preg_match( '/text\/javascript/', $response_body )
467 ) {
468 self::ThrowCloudFlareDDoSException( $response_body );
469 } 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 ) &&
470 preg_match( '/squid/', $response_body )
471 ) {
472 self::ThrowSquidAclException( $response_body );
473 } else {
474 $decoded = (object) array(
475 'error' => (object) array(
476 'type' => 'Unknown',
477 'message' => $response_body,
478 'code' => 'unknown',
479 'http' => 402
480 )
481 );
482 }
483 }
484
485 return $decoded;
486 }
487
488
489 /**
490 * Makes an HTTP request. This method can be overridden by subclasses if
491 * developers want to do fancier things or use something other than wp_remote_request()
492 * to make the request.
493 *
494 * @param string $pCanonizedPath The URL to make the request to
495 * @param string $pMethod HTTP method
496 * @param array $pParams The parameters to use for the POST body
497 * @param null|array $pWPRemoteArgs wp_remote_request options.
498 *
499 * @return object[]|object|null
500 *
501 * @throws Freemius_Exception
502 */
503 public function MakeRequest(
504 $pCanonizedPath,
505 $pMethod = 'GET',
506 $pParams = array(),
507 $pWPRemoteArgs = null
508 ) {
509 $resource = explode( '?', $pCanonizedPath );
510
511 // Only sign request if not ping.json connectivity test.
512 $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) );
513
514 return self::MakeStaticRequest(
515 $pCanonizedPath,
516 $pMethod,
517 $pParams,
518 $pWPRemoteArgs,
519 $this->_isSandbox,
520 $sign_request ? array( &$this, 'SignRequest' ) : null
521 );
522 }
523
524 /**
525 * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter
526 *
527 * @param resource $handle A cURL handle returned by curl_init()
528 *
529 * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to
530 * CURL_IPRESOLVE_V4
531 *
532 * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e
533 */
534 static function CurlResolveToIPv4( $handle ) {
535 curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
536
537 return $handle;
538 }
539
540 #----------------------------------------------------------------------------------
541 #region Connectivity Test
542 #----------------------------------------------------------------------------------
543
544 /**
545 * If successful connectivity to the API endpoint using ping.json endpoint.
546 *
547 * - OR -
548 *
549 * Validate if ping result object is valid.
550 *
551 * @param mixed $pPong
552 *
553 * @return bool
554 */
555 public static function Test( $pPong = null ) {
556 $pong = is_null( $pPong ) ?
557 self::Ping() :
558 $pPong;
559
560 return (
561 is_object( $pong ) &&
562 isset( $pong->api ) &&
563 'pong' === $pong->api
564 );
565 }
566
567 /**
568 * Ping API to test connectivity.
569 *
570 * @return object
571 */
572 public static function Ping() {
573 try {
574 $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' );
575 } catch ( Freemius_Exception $e ) {
576 // Map to error object.
577 $result = (object) $e->getResult();
578 } catch ( Exception $e ) {
579 // Map to error object.
580 $result = (object) array(
581 'error' => (object) array(
582 'type' => 'Unknown',
583 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')',
584 'code' => 'unknown',
585 'http' => 402
586 )
587 );
588 }
589
590 return $result;
591 }
592
593 #endregion
594
595 #----------------------------------------------------------------------------------
596 #region Connectivity Exceptions
597 #----------------------------------------------------------------------------------
598
599 /**
600 * @param \WP_Error $pError
601 *
602 * @return bool
603 */
604 private static function IsCurlError( WP_Error $pError ) {
605 $message = $pError->get_error_message( 'http_request_failed' );
606
607 return ( 0 === strpos( $message, 'cURL' ) );
608 }
609
610 /**
611 * @param WP_Error $pError
612 *
613 * @throws Freemius_Exception
614 */
615 private static function ThrowWPRemoteException( WP_Error $pError ) {
616 if ( self::IsCurlError( $pError ) ) {
617 $message = $pError->get_error_message( 'http_request_failed' );
618
619 #region Check if there are any missing cURL methods.
620
621 $curl_required_methods = array(
622 'curl_version',
623 'curl_exec',
624 'curl_init',
625 'curl_close',
626 'curl_setopt',
627 'curl_setopt_array',
628 'curl_error',
629 );
630
631 // Find all missing methods.
632 $missing_methods = array();
633 foreach ( $curl_required_methods as $m ) {
634 if ( ! function_exists( $m ) ) {
635 $missing_methods[] = $m;
636 }
637 }
638
639 if ( ! empty( $missing_methods ) ) {
640 throw new Freemius_Exception( array(
641 'error' => (object) array(
642 'type' => 'cUrlMissing',
643 'message' => $message,
644 'code' => 'curl_missing',
645 'http' => 402
646 ),
647 'missing_methods' => $missing_methods,
648 ) );
649 }
650
651 #endregion
652
653 // cURL error - "cURL error {{errno}}: {{error}}".
654 $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 );
655
656 $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed';
657 $message = ( 1 < count( $parts ) ) ? $parts[1] : $message;
658
659 $e = new Freemius_Exception( array(
660 'error' => (object) array(
661 'code' => $code,
662 'message' => $message,
663 'type' => 'CurlException',
664 ),
665 ) );
666 } else {
667 $e = new Freemius_Exception( array(
668 'error' => (object) array(
669 'code' => $pError->get_error_code(),
670 'message' => $pError->get_error_message(),
671 'type' => 'WPRemoteException',
672 ),
673 ) );
674 }
675
676 throw $e;
677 }
678
679 /**
680 * @param string $pResult
681 *
682 * @throws Freemius_Exception
683 */
684 private static function ThrowCloudFlareDDoSException( $pResult = '' ) {
685 throw new Freemius_Exception( array(
686 'error' => (object) array(
687 'type' => 'CloudFlareDDoSProtection',
688 'message' => $pResult,
689 'code' => 'cloudflare_ddos_protection',
690 'http' => 402
691 )
692 ) );
693 }
694
695 /**
696 * @param string $pResult
697 *
698 * @throws Freemius_Exception
699 */
700 private static function ThrowSquidAclException( $pResult = '' ) {
701 throw new Freemius_Exception( array(
702 'error' => (object) array(
703 'type' => 'SquidCacheBlock',
704 'message' => $pResult,
705 'code' => 'squid_cache_block',
706 'http' => 402
707 )
708 ) );
709 }
710
711 #endregion
712 }