Freemius_Api

The NextGEN Gallery Freemius Api class.

Defined (1)

The class is defined in the following location(s).

/freemius/includes/sdk/Freemius.php  
  1. class Freemius_Api extends Freemius_Api_Base { 
  2. private static $_logger = array(); 
  3.  
  4. /** 
  5. * @param string $pScope 'app', 'developer', 'user' or 'install'. 
  6. * @param number $pID Element's id. 
  7. * @param string $pPublic Public key. 
  8. * @param string|bool $pSecret Element's secret key. 
  9. * @param bool $pSandbox Whether or not to run API in sandbox mode. 
  10. */ 
  11. public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { 
  12. // If secret key not provided, use public key encryption. 
  13. if ( is_bool( $pSecret ) ) { 
  14. $pSecret = $pPublic; 
  15.  
  16. parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); 
  17.  
  18. public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { 
  19. $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); 
  20.  
  21. if ( ':' === $address[0] ) { 
  22. $address = self::$_protocol . $address; 
  23.  
  24. return $address . $pCanonizedPath; 
  25.  
  26. #region Servers Clock Diff ------------------------------------------------------ 
  27.  
  28. /** 
  29. * @var int Clock diff in seconds between current server to API server. 
  30. */ 
  31. private static $_clock_diff = 0; 
  32.  
  33. /** 
  34. * Set clock diff for all API calls. 
  35. * @since 1.0.3 
  36. * @param $pSeconds 
  37. */ 
  38. public static function SetClockDiff( $pSeconds ) { 
  39. self::$_clock_diff = $pSeconds; 
  40.  
  41. /** 
  42. * Find clock diff between current server to API server. 
  43. * @since 1.0.2 
  44. * @return int Clock diff in seconds. 
  45. */ 
  46. public static function FindClockDiff() { 
  47. $time = time(); 
  48. $pong = self::Ping(); 
  49.  
  50. return ( $time - strtotime( $pong->timestamp ) ); 
  51.  
  52. #endregion Servers Clock Diff ------------------------------------------------------ 
  53.  
  54. /** 
  55. * @var string http or https 
  56. */ 
  57. private static $_protocol = FS_API__PROTOCOL; 
  58.  
  59. /** 
  60. * Set API connection protocol. 
  61. * @since 1.0.4 
  62. */ 
  63. public static function SetHttp() { 
  64. self::$_protocol = 'http'; 
  65.  
  66. /** 
  67. * @since 1.0.4 
  68. * @return bool 
  69. */ 
  70. public static function IsHttps() { 
  71. return ( 'https' === self::$_protocol ); 
  72.  
  73. /** 
  74. * Sign request with the following HTTP headers: 
  75. * Content-MD5: MD5(HTTP Request body) 
  76. * Date: Current date (i.e Sat, 14 Feb 2015 20:24:46 +0000) 
  77. * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign,  
  78. * {scope_entity_secret_key})) 
  79. * @param string $pResourceUrl 
  80. * @param array $pCurlOptions 
  81. * @return array 
  82. */ 
  83. function SignRequest( $pResourceUrl, $pCurlOptions ) { 
  84. $eol = "\n"; 
  85. $content_md5 = ''; 
  86. $now = ( time() - self::$_clock_diff ); 
  87. $date = date( 'r', $now ); 
  88. $content_type = ''; 
  89.  
  90. if ( isset( $pCurlOptions[ CURLOPT_POST ] ) && 0 < $pCurlOptions[ CURLOPT_POST ] ) { 
  91. $content_md5 = md5( $pCurlOptions[ CURLOPT_POSTFIELDS ] ); 
  92. $pCurlOptions[ CURLOPT_HTTPHEADER ][] = 'Content-MD5: ' . $content_md5; 
  93. $content_type = 'application/json'; 
  94.  
  95. $pCurlOptions[ CURLOPT_HTTPHEADER ][] = 'Date: ' . $date; 
  96.  
  97. $string_to_sign = implode( $eol, array( 
  98. $pCurlOptions[ CURLOPT_CUSTOMREQUEST ],  
  99. $content_md5,  
  100. $content_type,  
  101. $date,  
  102. $pResourceUrl 
  103. ) ); 
  104.  
  105. // If secret and public keys are identical, it means that 
  106. // the signature uses public key hash encoding. 
  107. $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; 
  108.  
  109. // Add authorization header. 
  110. $pCurlOptions[ CURLOPT_HTTPHEADER ][] = 'Authorization: ' . 
  111. $auth_type . ' ' . 
  112. $this->_id . ':' . 
  113. $this->_public . ':' . 
  114. self::Base64UrlEncode( 
  115. hash_hmac( 'sha256', $string_to_sign, $this->_secret ) 
  116. ); 
  117.  
  118. return $pCurlOptions; 
  119.  
  120. /** 
  121. * Get API request URL signed via query string. 
  122. * @param string $pPath 
  123. * @throws Freemius_Exception 
  124. * @return string 
  125. */ 
  126. function GetSignedUrl( $pPath ) { 
  127. $resource = explode( '?', $this->CanonizePath( $pPath ) ); 
  128. $pResourceUrl = $resource[0]; 
  129.  
  130. $eol = "\n"; 
  131. $content_md5 = ''; 
  132. $content_type = ''; 
  133. $now = ( time() - self::$_clock_diff ); 
  134. $date = date( 'r', $now ); 
  135.  
  136. $string_to_sign = implode( $eol, array( 
  137. 'GET',  
  138. $content_md5,  
  139. $content_type,  
  140. $date,  
  141. $pResourceUrl 
  142. ) ); 
  143.  
  144. // If secret and public keys are identical, it means that 
  145. // the signature uses public key hash encoding. 
  146. $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; 
  147.  
  148. return Freemius_Api::GetUrl( 
  149. $pResourceUrl . '?' . 
  150. ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . 
  151. http_build_query( array( 
  152. 'auth_date' => $date,  
  153. 'authorization' => $auth_type . ' ' . $this->_id . ':' . 
  154. $this->_public . ':' . 
  155. self::Base64UrlEncode( hash_hmac( 
  156. 'sha256', $string_to_sign, $this->_secret 
  157. ) ) 
  158. ) ), $this->_isSandbox ); 
  159.  
  160. /** 
  161. * @param resource $pCurlHandler 
  162. * @param array $pCurlOptions 
  163. * @return mixed 
  164. */ 
  165. private static function ExecuteRequest( &$pCurlHandler, &$pCurlOptions ) { 
  166. $start = microtime( true ); 
  167.  
  168. $result = curl_exec( $pCurlHandler ); 
  169.  
  170. if ( FS_API__LOGGER_ON ) { 
  171. $end = microtime( true ); 
  172.  
  173. $has_body = ( isset( $pCurlOptions[ CURLOPT_POST ] ) && 0 < $pCurlOptions[ CURLOPT_POST ] ); 
  174.  
  175. self::$_logger[] = array( 
  176. 'id' => count( self::$_logger ),  
  177. 'start' => $start,  
  178. 'end' => $end,  
  179. 'total' => ( $end - $start ),  
  180. 'method' => $pCurlOptions[ CURLOPT_CUSTOMREQUEST ],  
  181. 'path' => $pCurlOptions[ CURLOPT_URL ],  
  182. 'body' => $has_body ? $pCurlOptions[ CURLOPT_POSTFIELDS ] : null,  
  183. 'result' => $result,  
  184. 'code' => curl_getinfo( $pCurlHandler, CURLINFO_HTTP_CODE ),  
  185. 'backtrace' => debug_backtrace(),  
  186. ); 
  187.  
  188. return $result; 
  189.  
  190. /** 
  191. * @return array 
  192. */ 
  193. static function GetLogger() { 
  194. return self::$_logger; 
  195.  
  196. /** 
  197. * @param string $pCanonizedPath 
  198. * @param string $pMethod 
  199. * @param array $pParams 
  200. * @param null|resource $pCurlHandler 
  201. * @param bool $pIsSandbox 
  202. * @param null|callable $pBeforeExecutionFunction 
  203. * @return object[]|object|null 
  204. * @throws \Freemius_Exception 
  205. */ 
  206. private static function MakeStaticRequest( 
  207. $pCanonizedPath,  
  208. $pMethod = 'GET',  
  209. $pParams = array(),  
  210. $pCurlHandler = null,  
  211. $pIsSandbox = false,  
  212. $pBeforeExecutionFunction = null 
  213. ) { 
  214. if ( ! FS_SDK__HAS_CURL ) { 
  215. self::ThrowNoCurlException(); 
  216.  
  217. // Connectivity errors simulation. 
  218. if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { 
  219. self::ThrowCloudFlareDDoSException(); 
  220. } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { 
  221. self::ThrowSquidAclException(); 
  222.  
  223. if ( ! $pCurlHandler ) { 
  224. $pCurlHandler = curl_init(); 
  225.  
  226. $opts = array( 
  227. CURLOPT_CONNECTTIMEOUT => 10,  
  228. CURLOPT_RETURNTRANSFER => true,  
  229. CURLOPT_TIMEOUT => 60,  
  230. CURLOPT_USERAGENT => FS_SDK__USER_AGENT,  
  231. CURLOPT_HTTPHEADER => array(),  
  232. ); 
  233.  
  234. if ( 'POST' === $pMethod || 'PUT' === $pMethod ) { 
  235. if ( is_array( $pParams ) && 0 < count( $pParams ) ) { 
  236. $opts[ CURLOPT_HTTPHEADER ][] = 'Content-Type: application/json'; 
  237. $opts[ CURLOPT_POST ] = count( $pParams ); 
  238. $opts[ CURLOPT_POSTFIELDS ] = json_encode( $pParams ); 
  239.  
  240. $opts[ CURLOPT_RETURNTRANSFER ] = true; 
  241.  
  242. $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); 
  243.  
  244. $opts[ CURLOPT_URL ] = $request_url; 
  245. $opts[ CURLOPT_CUSTOMREQUEST ] = $pMethod; 
  246.  
  247. $resource = explode( '?', $pCanonizedPath ); 
  248.  
  249. // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait 
  250. // for 2 seconds if the server does not support this header. 
  251. $opts[ CURLOPT_HTTPHEADER ][] = 'Expect:'; 
  252.  
  253. if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { 
  254. $opts[ CURLOPT_SSL_VERIFYHOST ] = false; 
  255. $opts[ CURLOPT_SSL_VERIFYPEER ] = false; 
  256.  
  257. if ( false !== $pBeforeExecutionFunction && 
  258. is_callable( $pBeforeExecutionFunction ) 
  259. ) { 
  260. $opts = call_user_func( $pBeforeExecutionFunction, $resource[0], $opts ); 
  261.  
  262. curl_setopt_array( $pCurlHandler, $opts ); 
  263. $result = self::ExecuteRequest( $pCurlHandler, $opts ); 
  264.  
  265. /**if (curl_errno($ch) == 60) // CURLE_SSL_CACERT 
  266. self::errorLog('Invalid or no certificate authority found, using bundled information'); 
  267. curl_setopt($ch, CURLOPT_CAINFO,  
  268. dirname(__FILE__) . '/fb_ca_chain_bundle.crt'); 
  269. $result = curl_exec($ch); 
  270. }*/ 
  271.  
  272. // With dual stacked DNS responses, it's possible for a server to 
  273. // have IPv6 enabled but not have IPv6 connectivity. If this is 
  274. // the case, curl will try IPv4 first and if that fails, then it will 
  275. // fall back to IPv6 and the error EHOSTUNREACH is returned by the 
  276. // operating system. 
  277. if ( false === $result && empty( $opts[ CURLOPT_IPRESOLVE ] ) ) { 
  278. $matches = array(); 
  279. $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; 
  280. if ( preg_match( $regex, curl_error( $pCurlHandler ), $matches ) ) { 
  281. if ( strlen( @inet_pton( $matches[1] ) ) === 16 ) { 
  282. // self::errorLog('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); 
  283. $opts[ CURLOPT_IPRESOLVE ] = CURL_IPRESOLVE_V4; 
  284. curl_setopt( $pCurlHandler, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); 
  285. $result = self::ExecuteRequest( $pCurlHandler, $opts ); 
  286.  
  287. if ( $result === false ) { 
  288. self::ThrowCurlException( $pCurlHandler ); 
  289.  
  290. curl_close( $pCurlHandler ); 
  291.  
  292. if ( empty( $result ) ) { 
  293. return null; 
  294.  
  295. $decoded = json_decode( $result ); 
  296.  
  297. if ( is_null( $decoded ) ) { 
  298. if ( preg_match( '/Please turn JavaScript on/i', $result ) && 
  299. preg_match( '/text\/javascript/', $result ) 
  300. ) { 
  301. self::ThrowCloudFlareDDoSException( $result ); 
  302. } 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 ) && 
  303. preg_match( '/squid/', $result ) 
  304. ) { 
  305. self::ThrowSquidAclException( $result ); 
  306. } else { 
  307. $decoded = (object) array( 
  308. 'error' => (object) array( 
  309. 'type' => 'Unknown',  
  310. 'message' => $result,  
  311. 'code' => 'unknown',  
  312. 'http' => 402 
  313. ); 
  314.  
  315. return $decoded; 
  316.  
  317.  
  318. /** 
  319. * Makes an HTTP request. This method can be overridden by subclasses if 
  320. * developers want to do fancier things or use something other than curl to 
  321. * make the request. 
  322. * @param string $pCanonizedPath The URL to make the request to 
  323. * @param string $pMethod HTTP method 
  324. * @param array $pParams The parameters to use for the POST body 
  325. * @param null|resource $pCurlHandler Initialized curl handle 
  326. * @return object[]|object|null 
  327. * @throws Freemius_Exception 
  328. */ 
  329. public function MakeRequest( 
  330. $pCanonizedPath,  
  331. $pMethod = 'GET',  
  332. $pParams = array(),  
  333. $pCurlHandler = null 
  334. ) { 
  335. $resource = explode( '?', $pCanonizedPath ); 
  336.  
  337. // Only sign request if not ping.json connectivity test. 
  338. $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); 
  339.  
  340. return self::MakeStaticRequest( 
  341. $pCanonizedPath,  
  342. $pMethod,  
  343. $pParams,  
  344. $pCurlHandler,  
  345. $this->_isSandbox,  
  346. $sign_request ? array( &$this, 'SignRequest' ) : null 
  347. ); 
  348.  
  349. #region Connectivity Test ------------------------------------------------------ 
  350.  
  351. /** 
  352. * If successful connectivity to the API endpoint using ping.json endpoint. 
  353. * - OR - 
  354. * Validate if ping result object is valid. 
  355. * @param mixed $pPong 
  356. * @return bool 
  357. */ 
  358. public static function Test( $pPong = null ) { 
  359. $pong = is_null( $pPong ) ? 
  360. self::Ping() : 
  361. $pPong; 
  362.  
  363. return ( 
  364. is_object( $pong ) && 
  365. isset( $pong->api ) && 
  366. 'pong' === $pong->api 
  367. ); 
  368.  
  369. /** 
  370. * Ping API to test connectivity. 
  371. * @return object 
  372. */ 
  373. public static function Ping() { 
  374. try { 
  375. $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); 
  376. } catch ( Freemius_Exception $e ) { 
  377. // Map to error object. 
  378. $result = (object) $e->getResult(); 
  379. } catch ( Exception $e ) { 
  380. // Map to error object. 
  381. $result = (object) array( 
  382. 'error' => array( 
  383. 'type' => 'Unknown',  
  384. 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')',  
  385. 'code' => 'unknown',  
  386. 'http' => 402 
  387. ); 
  388.  
  389. return $result; 
  390.  
  391. #endregion Connectivity Test ------------------------------------------------------ 
  392.  
  393. #region Connectivity Exceptions ------------------------------------------------------ 
  394.  
  395. /** 
  396. * @param resource $pCurlHandler 
  397. * @throws Freemius_Exception 
  398. */ 
  399. private static function ThrowCurlException( $pCurlHandler ) { 
  400. $e = new Freemius_Exception( array( 
  401. 'error' => array( 
  402. 'code' => curl_errno( $pCurlHandler ),  
  403. 'message' => curl_error( $pCurlHandler ),  
  404. 'type' => 'CurlException',  
  405. ),  
  406. ) ); 
  407.  
  408. curl_close( $pCurlHandler ); 
  409. throw $e; 
  410.  
  411. /** 
  412. * @param string $pResult 
  413. * @throws Freemius_Exception 
  414. */ 
  415. private static function ThrowNoCurlException( $pResult = '' ) { 
  416. throw new Freemius_Exception( array( 
  417. 'error' => (object) array( 
  418. 'type' => 'cUrlMissing',  
  419. 'message' => $pResult,  
  420. 'code' => 'curl_missing',  
  421. 'http' => 402 
  422. ) ); 
  423.  
  424. /** 
  425. * @param string $pResult 
  426. * @throws Freemius_Exception 
  427. */ 
  428. private static function ThrowCloudFlareDDoSException( $pResult = '' ) { 
  429. throw new Freemius_Exception( array( 
  430. 'error' => (object) array( 
  431. 'type' => 'CloudFlareDDoSProtection',  
  432. 'message' => $pResult,  
  433. 'code' => 'cloudflare_ddos_protection',  
  434. 'http' => 402 
  435. ) ); 
  436.  
  437. /** 
  438. * @param string $pResult 
  439. * @throws Freemius_Exception 
  440. */ 
  441. private static function ThrowSquidAclException( $pResult = '' ) { 
  442. throw new Freemius_Exception( array( 
  443. 'error' => (object) array( 
  444. 'type' => 'SquidCacheBlock',  
  445. 'message' => $pResult,  
  446. 'code' => 'squid_cache_block',  
  447. 'http' => 402 
  448. ) ); 
  449.  
  450. #endregion Connectivity Exceptions ------------------------------------------------------