FS_Api

Class FS_Api.

Defined (1)

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

/freemius/includes/class-fs-api.php  
  1. class FS_Api { 
  2. /** 
  3. * @var FS_Api[] 
  4. */ 
  5. private static $_instances = array(); 
  6.  
  7. /** 
  8. * @var FS_Option_Manager Freemius options, options-manager. 
  9. */ 
  10. private static $_options; 
  11.  
  12. /** 
  13. * @var FS_Cache_Manager API Caching layer 
  14. */ 
  15. private static $_cache; 
  16.  
  17. /** 
  18. * @var int Clock diff in seconds between current server to API server. 
  19. */ 
  20. private static $_clock_diff; 
  21.  
  22. /** 
  23. * @var Freemius_Api 
  24. */ 
  25. private $_api; 
  26.  
  27. /** 
  28. * @var string 
  29. */ 
  30. private $_slug; 
  31.  
  32. /** 
  33. * @var FS_Logger 
  34. * @since 1.0.4 
  35. */ 
  36. private $_logger; 
  37.  
  38. /** 
  39. * @param string $slug 
  40. * @param string $scope 'app', 'developer', 'user' or 'install'. 
  41. * @param number $id Element's id. 
  42. * @param string $public_key Public key. 
  43. * @param bool $is_sandbox 
  44. * @param bool|string $secret_key Element's secret key. 
  45. * @return FS_Api 
  46. */ 
  47. static function instance( $slug, $scope, $id, $public_key, $is_sandbox, $secret_key = false ) { 
  48. $identifier = md5( $slug . $scope . $id . $public_key . ( is_string( $secret_key ) ? $secret_key : '' ) . json_encode( $is_sandbox ) ); 
  49.  
  50. if ( ! isset( self::$_instances[ $identifier ] ) ) { 
  51. self::_init(); 
  52.  
  53. self::$_instances[ $identifier ] = new FS_Api( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox ); 
  54.  
  55. return self::$_instances[ $identifier ]; 
  56.  
  57. private static function _init() { 
  58. if ( isset( self::$_options ) ) { 
  59. return; 
  60.  
  61. if ( ! class_exists( 'Freemius_Api' ) ) { 
  62. require_once( WP_FS__DIR_SDK . '/Freemius.php' ); 
  63.  
  64. self::$_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true ); 
  65. self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME ); 
  66.  
  67. self::$_clock_diff = self::$_options->get_option( 'api_clock_diff', 0 ); 
  68. Freemius_Api::SetClockDiff( self::$_clock_diff ); 
  69.  
  70. if ( self::$_options->get_option( 'api_force_http', false ) ) { 
  71. Freemius_Api::SetHttp(); 
  72.  
  73. /** 
  74. * @param string $slug 
  75. * @param string $scope 'app', 'developer', 'user' or 'install'. 
  76. * @param number $id Element's id. 
  77. * @param string $public_key Public key. 
  78. * @param bool|string $secret_key Element's secret key. 
  79. * @param bool $is_sandbox 
  80. */ 
  81. private function __construct( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox ) { 
  82. $this->_api = new Freemius_Api( $scope, $id, $public_key, $secret_key, $is_sandbox ); 
  83.  
  84. $this->_slug = $slug; 
  85. $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); 
  86.  
  87. /** 
  88. * Find clock diff between server and API server, and store the diff locally. 
  89. * @param bool|int $diff 
  90. * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds. 
  91. */ 
  92. private function _sync_clock_diff( $diff = false ) { 
  93. $this->_logger->entrance(); 
  94.  
  95. // Sync clock and store. 
  96. $new_clock_diff = ( false === $diff ) ? 
  97. Freemius_Api::FindClockDiff() : 
  98. $diff; 
  99.  
  100. if ( $new_clock_diff === self::$_clock_diff ) { 
  101. return false; 
  102.  
  103. self::$_clock_diff = $new_clock_diff; 
  104.  
  105. // Update API clock's diff. 
  106. Freemius_Api::SetClockDiff( self::$_clock_diff ); 
  107.  
  108. // Store new clock diff in storage. 
  109. self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true ); 
  110.  
  111. return $new_clock_diff; 
  112.  
  113. /** 
  114. * Override API call to enable retry with servers' clock auto sync method. 
  115. * @param string $path 
  116. * @param string $method 
  117. * @param array $params 
  118. * @param bool $retry Is in retry or first call attempt. 
  119. * @return array|mixed|string|void 
  120. */ 
  121. private function _call( $path, $method = 'GET', $params = array(), $retry = false ) { 
  122. $this->_logger->entrance( $method . ':' . $path ); 
  123.  
  124. if ( self::is_temporary_down() ) { 
  125. $result = $this->get_temporary_unavailable_error(); 
  126. } else { 
  127. $result = $this->_api->Api( $path, $method, $params ); 
  128.  
  129. if ( null !== $result && 
  130. isset( $result->error ) && 
  131. isset( $result->error->code ) && 
  132. 'request_expired' === $result->error->code 
  133. ) { 
  134. if ( ! $retry ) { 
  135. $diff = isset( $result->error->timestamp ) ? 
  136. ( time() - strtotime( $result->error->timestamp ) ) : 
  137. false; 
  138.  
  139. // Try to sync clock diff. 
  140. if ( false !== $this->_sync_clock_diff( $diff ) ) { 
  141. // Retry call with new synced clock. 
  142. return $this->_call( $path, $method, $params, true ); 
  143.  
  144. if ( null !== $result && isset( $result->error ) && isset( $result->error->message ) ) { 
  145. // Log API errors. 
  146. $this->_logger->error( $result->error->message ); 
  147.  
  148. return $result; 
  149.  
  150. /** 
  151. * Override API call to wrap it in servers' clock sync method. 
  152. * @param string $path 
  153. * @param string $method 
  154. * @param array $params 
  155. * @return array|mixed|string|void 
  156. * @throws Freemius_Exception 
  157. */ 
  158. function call( $path, $method = 'GET', $params = array() ) { 
  159. return $this->_call( $path, $method, $params ); 
  160.  
  161. /** 
  162. * Get API request URL signed via query string. 
  163. * @param string $path 
  164. * @return string 
  165. */ 
  166. function get_signed_url( $path ) { 
  167. return $this->_api->GetSignedUrl( $path ); 
  168.  
  169. /** 
  170. * @param string $path 
  171. * @param bool $flush 
  172. * @param int $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours 
  173. * @return stdClass|mixed 
  174. */ 
  175. function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { 
  176. $this->_logger->entrance( $path ); 
  177.  
  178. $cache_key = $this->get_cache_key( $path ); 
  179.  
  180. // Always flush during development. 
  181. if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) { 
  182. $flush = true; 
  183.  
  184. $cached_result = self::$_cache->get( $cache_key ); 
  185.  
  186. if ( $flush || ! self::$_cache->has_valid( $cache_key ) ) { 
  187. $result = $this->call( $path ); 
  188.  
  189. if ( ! is_object( $result ) || isset( $result->error ) ) { 
  190. // Api returned an error. 
  191. if ( is_object( $cached_result ) && 
  192. ! isset( $cached_result ) 
  193. ) { 
  194. // If there was an error during a newer data fetch,  
  195. // fallback to older data version. 
  196. $result = $cached_result; 
  197. } else { 
  198. // If no older data version, return result without 
  199. // caching the error. 
  200. return $result; 
  201.  
  202. self::$_cache->set( $cache_key, $result, $expiration ); 
  203.  
  204. $cached_result = $result; 
  205.  
  206. return $cached_result; 
  207.  
  208. /** 
  209. * Check if there's a cached version of the API request. 
  210. * @author Vova Feldman (@svovaf) 
  211. * @since 1.2.1 
  212. * @param string $path 
  213. * @param string $method 
  214. * @param array $params 
  215. * @return bool 
  216. */ 
  217. function is_cached( $path, $method = 'GET', $params = array() ) 
  218. $cache_key = $this->get_cache_key( $path, $method, $params ); 
  219.  
  220. return self::$_cache->has_valid( $cache_key ); 
  221.  
  222. /** 
  223. * @param string $path 
  224. * @param string $method 
  225. * @param array $params 
  226. * @return string 
  227. * @throws \Freemius_Exception 
  228. */ 
  229. private function get_cache_key( $path, $method = 'GET', $params = array() ) { 
  230. $canonized = $this->_api->CanonizePath( $path ); 
  231. // $exploded = explode('/', $canonized); 
  232. // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params)); 
  233. return strtolower($method . ':' . $canonized) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' ); 
  234.  
  235. /** 
  236. * Test API connectivity. 
  237. * @author Vova Feldman (@svovaf) 
  238. * @since 1.0.9 If fails, try to fallback to HTTP. 
  239. * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if 
  240. * temporary down. 
  241. * @return bool True if successful connectivity to the API. 
  242. */ 
  243. static function test() { 
  244. self::_init(); 
  245.  
  246. $cache_key = 'ping_test'; 
  247.  
  248. $test = self::$_cache->get_valid( $cache_key, null ); 
  249.  
  250. if ( is_null( $test ) ) { 
  251. $test = Freemius_Api::Test(); 
  252.  
  253. if ( false === $test && Freemius_Api::IsHttps() ) { 
  254. // Fallback to HTTP, since HTTPS fails. 
  255. Freemius_Api::SetHttp(); 
  256.  
  257. self::$_options->set_option( 'api_force_http', true, true ); 
  258.  
  259. $test = Freemius_Api::Test(); 
  260.  
  261. if ( false === $test ) { 
  262. /** 
  263. * API connectivity test fail also in HTTP request, therefore,  
  264. * fallback to HTTPS to keep connection secure. 
  265. * @since 1.1.6 
  266. */ 
  267. self::$_options->set_option( 'api_force_http', false, true ); 
  268.  
  269. self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC ); 
  270.  
  271. return $test; 
  272.  
  273. /** 
  274. * Check if API is temporary down. 
  275. * @author Vova Feldman (@svovaf) 
  276. * @since 1.1.6 
  277. * @return bool 
  278. */ 
  279. static function is_temporary_down() { 
  280. self::_init(); 
  281.  
  282. $test = self::$_cache->get_valid( 'ping_test', null ); 
  283.  
  284. return ( false === $test ); 
  285.  
  286. /** 
  287. * @author Vova Feldman (@svovaf) 
  288. * @since 1.1.6 
  289. * @return object 
  290. */ 
  291. private function get_temporary_unavailable_error() { 
  292. return (object) array( 
  293. 'error' => array( 
  294. 'type' => 'TemporaryUnavailable',  
  295. 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.',  
  296. 'code' => 'temporary_unavailable',  
  297. 'http' => 503 
  298. ); 
  299.  
  300. /** 
  301. * Ping API for connectivity test, and return result object. 
  302. * @author Vova Feldman (@svovaf) 
  303. * @since 1.0.9 
  304. * @param null|string $unique_anonymous_id 
  305. * @param array $params 
  306. * @return object 
  307. */ 
  308. function ping( $unique_anonymous_id = null, $params = array() ) { 
  309. $this->_logger->entrance(); 
  310.  
  311. if ( self::is_temporary_down() ) { 
  312. return $this->get_temporary_unavailable_error(); 
  313.  
  314. $pong = is_null( $unique_anonymous_id ) ? 
  315. Freemius_Api::Ping() : 
  316. $this->_call( 'ping.json?' . http_build_query( array_merge( 
  317. array( 'uid' => $unique_anonymous_id ),  
  318. $params 
  319. ) ) ); 
  320.  
  321. if ( $this->is_valid_ping( $pong ) ) { 
  322. return $pong; 
  323.  
  324. if ( self::should_try_with_http( $pong ) ) { 
  325. // Fallback to HTTP, since HTTPS fails. 
  326. Freemius_Api::SetHttp(); 
  327.  
  328. self::$_options->set_option( 'api_force_http', true, true ); 
  329.  
  330. $pong = is_null( $unique_anonymous_id ) ? 
  331. Freemius_Api::Ping() : 
  332. $this->_call( 'ping.json?' . http_build_query( array_merge( 
  333. array( 'uid' => $unique_anonymous_id ),  
  334. $params 
  335. ) ) ); 
  336.  
  337. if ( ! $this->is_valid_ping( $pong ) ) { 
  338. self::$_options->set_option( 'api_force_http', false, true ); 
  339.  
  340. return $pong; 
  341.  
  342. /** 
  343. * Check if based on the API result we should try 
  344. * to re-run the same request with HTTP instead of HTTPS. 
  345. * @author Vova Feldman (@svovaf) 
  346. * @since 1.1.6 
  347. * @param $result 
  348. * @return bool 
  349. */ 
  350. private static function should_try_with_http( $result ) { 
  351. if ( ! Freemius_Api::IsHttps() ) { 
  352. return false; 
  353.  
  354. return ( ! is_object( $result ) || 
  355. ! isset( $result->error ) || 
  356. ! isset( $result->error->code ) || 
  357. ! in_array( $result->error->code, array( 
  358. 'curl_missing',  
  359. 'cloudflare_ddos_protection',  
  360. 'maintenance_mode',  
  361. 'squid_cache_block',  
  362. 'too_many_requests',  
  363. ) ) ); 
  364.  
  365.  
  366. /** 
  367. * Check if valid ping request result. 
  368. * @author Vova Feldman (@svovaf) 
  369. * @since 1.1.1 
  370. * @param mixed $pong 
  371. * @return bool 
  372. */ 
  373. function is_valid_ping( $pong ) { 
  374. return Freemius_Api::Test( $pong ); 
  375.  
  376. function get_url( $path = '' ) { 
  377. return Freemius_Api::GetUrl( $path, $this->_api->IsSandbox() ); 
  378.  
  379. /** 
  380. * Clear API cache. 
  381. * @author Vova Feldman (@svovaf) 
  382. * @since 1.0.9 
  383. */ 
  384. static function clear_cache() { 
  385. self::_init(); 
  386.  
  387. self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME ); 
  388. self::$_cache->clear( true );