PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 1.7.1
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v1.7.1
4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.14 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.4.0 4.4.1 4.4.10 4.4.11 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5.0 4.5.1
embedpress / freemius / includes / sdk / Freemius.php
embedpress / freemius / includes / sdk Last commit date
Exceptions 9 years ago Freemius.php 9 years ago FreemiusBase.php 9 years ago LICENSE.txt 9 years ago index.php 9 years ago
Freemius.php
583 lines
1 <?php
2 /**
3 * Copyright 2014 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 define( 'FS_SDK__HAS_CURL', ! FS_SDK__SIMULATE_NO_CURL && function_exists( 'curl_version' ) );
38 }
39
40 if ( ! FS_SDK__HAS_CURL ) {
41 $curl_version = array( 'version' => '7.0.0' );
42 } else {
43 $curl_version = curl_version();
44 }
45
46 if ( ! defined( 'FS_API__PROTOCOL' ) ) {
47 define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' );
48 }
49
50 if ( ! defined( 'FS_API__LOGGER_ON' ) ) {
51 define( 'FS_API__LOGGER_ON', false );
52 }
53
54 if ( ! defined( 'FS_API__ADDRESS' ) ) {
55 define( 'FS_API__ADDRESS', '://api.freemius.com' );
56 }
57 if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) {
58 define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' );
59 }
60
61 if ( class_exists( 'Freemius_Api' ) ) {
62 return;
63 }
64
65 class Freemius_Api extends Freemius_Api_Base {
66 private static $_logger = array();
67
68 /**
69 * @param string $pScope 'app', 'developer', 'user' or 'install'.
70 * @param number $pID Element's id.
71 * @param string $pPublic Public key.
72 * @param string|bool $pSecret Element's secret key.
73 * @param bool $pSandbox Whether or not to run API in sandbox mode.
74 */
75 public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) {
76 // If secret key not provided, use public key encryption.
77 if ( is_bool( $pSecret ) ) {
78 $pSecret = $pPublic;
79 }
80
81 parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox );
82 }
83
84 public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) {
85 $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS );
86
87 if ( ':' === $address[0] ) {
88 $address = self::$_protocol . $address;
89 }
90
91 return $address . $pCanonizedPath;
92 }
93
94 #region Servers Clock Diff ------------------------------------------------------
95
96 /**
97 * @var int Clock diff in seconds between current server to API server.
98 */
99 private static $_clock_diff = 0;
100
101 /**
102 * Set clock diff for all API calls.
103 *
104 * @since 1.0.3
105 *
106 * @param $pSeconds
107 */
108 public static function SetClockDiff( $pSeconds ) {
109 self::$_clock_diff = $pSeconds;
110 }
111
112 /**
113 * Find clock diff between current server to API server.
114 *
115 * @since 1.0.2
116 * @return int Clock diff in seconds.
117 */
118 public static function FindClockDiff() {
119 $time = time();
120 $pong = self::Ping();
121
122 return ( $time - strtotime( $pong->timestamp ) );
123 }
124
125 #endregion Servers Clock Diff ------------------------------------------------------
126
127 /**
128 * @var string http or https
129 */
130 private static $_protocol = FS_API__PROTOCOL;
131
132 /**
133 * Set API connection protocol.
134 *
135 * @since 1.0.4
136 */
137 public static function SetHttp() {
138 self::$_protocol = 'http';
139 }
140
141 /**
142 * @since 1.0.4
143 *
144 * @return bool
145 */
146 public static function IsHttps() {
147 return ( 'https' === self::$_protocol );
148 }
149
150 /**
151 * Sign request with the following HTTP headers:
152 * Content-MD5: MD5(HTTP Request body)
153 * Date: Current date (i.e Sat, 14 Feb 2015 20:24:46 +0000)
154 * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,
155 * {scope_entity_secret_key}))
156 *
157 * @param string $pResourceUrl
158 * @param array $pCurlOptions
159 *
160 * @return array
161 */
162 function SignRequest( $pResourceUrl, $pCurlOptions ) {
163 $eol = "\n";
164 $content_md5 = '';
165 $now = ( time() - self::$_clock_diff );
166 $date = date( 'r', $now );
167 $content_type = '';
168
169 if ( isset( $pCurlOptions[ CURLOPT_POST ] ) && 0 < $pCurlOptions[ CURLOPT_POST ] ) {
170 $content_md5 = md5( $pCurlOptions[ CURLOPT_POSTFIELDS ] );
171 $pCurlOptions[ CURLOPT_HTTPHEADER ][] = 'Content-MD5: ' . $content_md5;
172 $content_type = 'application/json';
173 }
174
175 $pCurlOptions[ CURLOPT_HTTPHEADER ][] = 'Date: ' . $date;
176
177 $string_to_sign = implode( $eol, array(
178 $pCurlOptions[ CURLOPT_CUSTOMREQUEST ],
179 $content_md5,
180 $content_type,
181 $date,
182 $pResourceUrl
183 ) );
184
185 // If secret and public keys are identical, it means that
186 // the signature uses public key hash encoding.
187 $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP';
188
189 // Add authorization header.
190 $pCurlOptions[ CURLOPT_HTTPHEADER ][] = 'Authorization: ' .
191 $auth_type . ' ' .
192 $this->_id . ':' .
193 $this->_public . ':' .
194 self::Base64UrlEncode(
195 hash_hmac( 'sha256', $string_to_sign, $this->_secret )
196 );
197
198 return $pCurlOptions;
199 }
200
201 /**
202 * Get API request URL signed via query string.
203 *
204 * @param string $pPath
205 *
206 * @throws Freemius_Exception
207 *
208 * @return string
209 */
210 function GetSignedUrl( $pPath ) {
211 $resource = explode( '?', $this->CanonizePath( $pPath ) );
212 $pResourceUrl = $resource[0];
213
214 $eol = "\n";
215 $content_md5 = '';
216 $content_type = '';
217 $now = ( time() - self::$_clock_diff );
218 $date = date( 'r', $now );
219
220 $string_to_sign = implode( $eol, array(
221 'GET',
222 $content_md5,
223 $content_type,
224 $date,
225 $pResourceUrl
226 ) );
227
228 // If secret and public keys are identical, it means that
229 // the signature uses public key hash encoding.
230 $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP';
231
232 return Freemius_Api::GetUrl(
233 $pResourceUrl . '?' .
234 ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) .
235 http_build_query( array(
236 'auth_date' => $date,
237 'authorization' => $auth_type . ' ' . $this->_id . ':' .
238 $this->_public . ':' .
239 self::Base64UrlEncode( hash_hmac(
240 'sha256', $string_to_sign, $this->_secret
241 ) )
242 ) ), $this->_isSandbox );
243 }
244
245 /**
246 * @param resource $pCurlHandler
247 * @param array $pCurlOptions
248 *
249 * @return mixed
250 */
251 private static function ExecuteRequest( &$pCurlHandler, &$pCurlOptions ) {
252 $start = microtime( true );
253
254 $result = curl_exec( $pCurlHandler );
255
256 if ( FS_API__LOGGER_ON ) {
257 $end = microtime( true );
258
259 $has_body = ( isset( $pCurlOptions[ CURLOPT_POST ] ) && 0 < $pCurlOptions[ CURLOPT_POST ] );
260
261 self::$_logger[] = array(
262 'id' => count( self::$_logger ),
263 'start' => $start,
264 'end' => $end,
265 'total' => ( $end - $start ),
266 'method' => $pCurlOptions[ CURLOPT_CUSTOMREQUEST ],
267 'path' => $pCurlOptions[ CURLOPT_URL ],
268 'body' => $has_body ? $pCurlOptions[ CURLOPT_POSTFIELDS ] : null,
269 'result' => $result,
270 'code' => curl_getinfo( $pCurlHandler, CURLINFO_HTTP_CODE ),
271 'backtrace' => debug_backtrace(),
272 );
273 }
274
275 return $result;
276 }
277
278 /**
279 * @return array
280 */
281 static function GetLogger() {
282 return self::$_logger;
283 }
284
285 /**
286 * @param string $pCanonizedPath
287 * @param string $pMethod
288 * @param array $pParams
289 * @param null|resource $pCurlHandler
290 * @param bool $pIsSandbox
291 * @param null|callable $pBeforeExecutionFunction
292 *
293 * @return object[]|object|null
294 *
295 * @throws \Freemius_Exception
296 */
297 private static function MakeStaticRequest(
298 $pCanonizedPath,
299 $pMethod = 'GET',
300 $pParams = array(),
301 $pCurlHandler = null,
302 $pIsSandbox = false,
303 $pBeforeExecutionFunction = null
304 ) {
305 if ( ! FS_SDK__HAS_CURL ) {
306 self::ThrowNoCurlException();
307 }
308
309 // Connectivity errors simulation.
310 if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) {
311 self::ThrowCloudFlareDDoSException();
312 } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) {
313 self::ThrowSquidAclException();
314 }
315
316 if ( ! $pCurlHandler ) {
317 $pCurlHandler = curl_init();
318 }
319
320 $opts = array(
321 CURLOPT_CONNECTTIMEOUT => 10,
322 CURLOPT_RETURNTRANSFER => true,
323 CURLOPT_TIMEOUT => 60,
324 CURLOPT_USERAGENT => FS_SDK__USER_AGENT,
325 CURLOPT_HTTPHEADER => array(),
326 );
327
328 if ( 'POST' === $pMethod || 'PUT' === $pMethod ) {
329 if ( is_array( $pParams ) && 0 < count( $pParams ) ) {
330 $opts[ CURLOPT_HTTPHEADER ][] = 'Content-Type: application/json';
331 $opts[ CURLOPT_POST ] = count( $pParams );
332 $opts[ CURLOPT_POSTFIELDS ] = json_encode( $pParams );
333 }
334
335 $opts[ CURLOPT_RETURNTRANSFER ] = true;
336 }
337
338 $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox );
339
340 $opts[ CURLOPT_URL ] = $request_url;
341 $opts[ CURLOPT_CUSTOMREQUEST ] = $pMethod;
342
343 $resource = explode( '?', $pCanonizedPath );
344
345 // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
346 // for 2 seconds if the server does not support this header.
347 $opts[ CURLOPT_HTTPHEADER ][] = 'Expect:';
348
349 if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) {
350 $opts[ CURLOPT_SSL_VERIFYHOST ] = false;
351 $opts[ CURLOPT_SSL_VERIFYPEER ] = false;
352 }
353
354 if ( false !== $pBeforeExecutionFunction &&
355 is_callable( $pBeforeExecutionFunction )
356 ) {
357 $opts = call_user_func( $pBeforeExecutionFunction, $resource[0], $opts );
358 }
359
360 curl_setopt_array( $pCurlHandler, $opts );
361 $result = self::ExecuteRequest( $pCurlHandler, $opts );
362
363 /*if (curl_errno($ch) == 60) // CURLE_SSL_CACERT
364 {
365 self::errorLog('Invalid or no certificate authority found, using bundled information');
366 curl_setopt($ch, CURLOPT_CAINFO,
367 dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
368 $result = curl_exec($ch);
369 }*/
370
371 // With dual stacked DNS responses, it's possible for a server to
372 // have IPv6 enabled but not have IPv6 connectivity. If this is
373 // the case, curl will try IPv4 first and if that fails, then it will
374 // fall back to IPv6 and the error EHOSTUNREACH is returned by the
375 // operating system.
376 if ( false === $result && empty( $opts[ CURLOPT_IPRESOLVE ] ) ) {
377 $matches = array();
378 $regex = '/Failed to connect to ([^:].*): Network is unreachable/';
379 if ( preg_match( $regex, curl_error( $pCurlHandler ), $matches ) ) {
380 if ( strlen( @inet_pton( $matches[1] ) ) === 16 ) {
381 // self::errorLog('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.');
382 $opts[ CURLOPT_IPRESOLVE ] = CURL_IPRESOLVE_V4;
383 curl_setopt( $pCurlHandler, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
384 $result = self::ExecuteRequest( $pCurlHandler, $opts );
385 }
386 }
387 }
388
389 if ( $result === false ) {
390 self::ThrowCurlException( $pCurlHandler );
391 }
392
393 curl_close( $pCurlHandler );
394
395 if ( empty( $result ) ) {
396 return null;
397 }
398
399 $decoded = json_decode( $result );
400
401 if ( is_null( $decoded ) ) {
402 if ( preg_match( '/Please turn JavaScript on/i', $result ) &&
403 preg_match( '/text\/javascript/', $result )
404 ) {
405 self::ThrowCloudFlareDDoSException( $result );
406 } 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./', $result ) &&
407 preg_match( '/squid/', $result )
408 ) {
409 self::ThrowSquidAclException( $result );
410 } else {
411 $decoded = (object) array(
412 'error' => (object) array(
413 'type' => 'Unknown',
414 'message' => $result,
415 'code' => 'unknown',
416 'http' => 402
417 )
418 );
419 }
420 }
421
422 return $decoded;
423 }
424
425
426 /**
427 * Makes an HTTP request. This method can be overridden by subclasses if
428 * developers want to do fancier things or use something other than curl to
429 * make the request.
430 *
431 * @param string $pCanonizedPath The URL to make the request to
432 * @param string $pMethod HTTP method
433 * @param array $pParams The parameters to use for the POST body
434 * @param null|resource $pCurlHandler Initialized curl handle
435 *
436 * @return object[]|object|null
437 *
438 * @throws Freemius_Exception
439 */
440 public function MakeRequest(
441 $pCanonizedPath,
442 $pMethod = 'GET',
443 $pParams = array(),
444 $pCurlHandler = null
445 ) {
446 $resource = explode( '?', $pCanonizedPath );
447
448 // Only sign request if not ping.json connectivity test.
449 $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) );
450
451 return self::MakeStaticRequest(
452 $pCanonizedPath,
453 $pMethod,
454 $pParams,
455 $pCurlHandler,
456 $this->_isSandbox,
457 $sign_request ? array( &$this, 'SignRequest' ) : null
458 );
459 }
460
461 #region Connectivity Test ------------------------------------------------------
462
463 /**
464 * If successful connectivity to the API endpoint using ping.json endpoint.
465 *
466 * - OR -
467 *
468 * Validate if ping result object is valid.
469 *
470 * @param mixed $pPong
471 *
472 * @return bool
473 */
474 public static function Test( $pPong = null ) {
475 $pong = is_null( $pPong ) ?
476 self::Ping() :
477 $pPong;
478
479 return (
480 is_object( $pong ) &&
481 isset( $pong->api ) &&
482 'pong' === $pong->api
483 );
484 }
485
486 /**
487 * Ping API to test connectivity.
488 *
489 * @return object
490 */
491 public static function Ping() {
492 try {
493 $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' );
494 } catch ( Freemius_Exception $e ) {
495 // Map to error object.
496 $result = (object) $e->getResult();
497 } catch ( Exception $e ) {
498 // Map to error object.
499 $result = (object) array(
500 'error' => array(
501 'type' => 'Unknown',
502 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')',
503 'code' => 'unknown',
504 'http' => 402
505 )
506 );
507 }
508
509 return $result;
510 }
511
512 #endregion Connectivity Test ------------------------------------------------------
513
514 #region Connectivity Exceptions ------------------------------------------------------
515
516 /**
517 * @param resource $pCurlHandler
518 *
519 * @throws Freemius_Exception
520 */
521 private static function ThrowCurlException( $pCurlHandler ) {
522 $e = new Freemius_Exception( array(
523 'error' => array(
524 'code' => curl_errno( $pCurlHandler ),
525 'message' => curl_error( $pCurlHandler ),
526 'type' => 'CurlException',
527 ),
528 ) );
529
530 curl_close( $pCurlHandler );
531 throw $e;
532 }
533
534 /**
535 * @param string $pResult
536 *
537 * @throws Freemius_Exception
538 */
539 private static function ThrowNoCurlException( $pResult = '' ) {
540 throw new Freemius_Exception( array(
541 'error' => (object) array(
542 'type' => 'cUrlMissing',
543 'message' => $pResult,
544 'code' => 'curl_missing',
545 'http' => 402
546 )
547 ) );
548 }
549
550 /**
551 * @param string $pResult
552 *
553 * @throws Freemius_Exception
554 */
555 private static function ThrowCloudFlareDDoSException( $pResult = '' ) {
556 throw new Freemius_Exception( array(
557 'error' => (object) array(
558 'type' => 'CloudFlareDDoSProtection',
559 'message' => $pResult,
560 'code' => 'cloudflare_ddos_protection',
561 'http' => 402
562 )
563 ) );
564 }
565
566 /**
567 * @param string $pResult
568 *
569 * @throws Freemius_Exception
570 */
571 private static function ThrowSquidAclException( $pResult = '' ) {
572 throw new Freemius_Exception( array(
573 'error' => (object) array(
574 'type' => 'SquidCacheBlock',
575 'message' => $pResult,
576 'code' => 'squid_cache_block',
577 'http' => 402
578 )
579 ) );
580 }
581
582 #endregion Connectivity Exceptions ------------------------------------------------------
583 }