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. private function get_cache_key( $path, $method = 'GET', $params = array() ) { 
  209. $canonized = $this->_api->CanonizePath( $path ); 
  210. // $exploded = explode('/', $canonized); 
  211. // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params)); 
  212. return $method . ':' . $canonized . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' ); 
  213.  
  214. /** 
  215. * Test API connectivity. 
  216. * @author Vova Feldman (@svovaf) 
  217. * @since 1.0.9 If fails, try to fallback to HTTP. 
  218. * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if 
  219. * temporary down. 
  220. * @return bool True if successful connectivity to the API. 
  221. */ 
  222. static function test() { 
  223. self::_init(); 
  224.  
  225. $cache_key = 'ping_test'; 
  226.  
  227. $test = self::$_cache->get_valid( $cache_key, null ); 
  228.  
  229. if ( is_null( $test ) ) { 
  230. $test = Freemius_Api::Test(); 
  231.  
  232. if ( false === $test && Freemius_Api::IsHttps() ) { 
  233. // Fallback to HTTP, since HTTPS fails. 
  234. Freemius_Api::SetHttp(); 
  235.  
  236. self::$_options->set_option( 'api_force_http', true, true ); 
  237.  
  238. $test = Freemius_Api::Test(); 
  239.  
  240. if ( false === $test ) { 
  241. /** 
  242. * API connectivity test fail also in HTTP request, therefore,  
  243. * fallback to HTTPS to keep connection secure. 
  244. * @since 1.1.6 
  245. */ 
  246. self::$_options->set_option( 'api_force_http', false, true ); 
  247.  
  248. self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC ); 
  249.  
  250. return $test; 
  251.  
  252. /** 
  253. * Check if API is temporary down. 
  254. * @author Vova Feldman (@svovaf) 
  255. * @since 1.1.6 
  256. * @return bool 
  257. */ 
  258. static function is_temporary_down() { 
  259. self::_init(); 
  260.  
  261. $test = self::$_cache->get_valid( 'ping_test', null ); 
  262.  
  263. return ( false === $test ); 
  264.  
  265. /** 
  266. * @author Vova Feldman (@svovaf) 
  267. * @since 1.1.6 
  268. * @return object 
  269. */ 
  270. private function get_temporary_unavailable_error() { 
  271. return (object) array( 
  272. 'error' => array( 
  273. 'type' => 'TemporaryUnavailable',  
  274. 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.',  
  275. 'code' => 'temporary_unavailable',  
  276. 'http' => 503 
  277. ); 
  278.  
  279. /** 
  280. * Ping API for connectivity test, and return result object. 
  281. * @author Vova Feldman (@svovaf) 
  282. * @since 1.0.9 
  283. * @param null|string $unique_anonymous_id 
  284. * @param array $params 
  285. * @return object 
  286. */ 
  287. function ping( $unique_anonymous_id = null, $params = array() ) { 
  288. $this->_logger->entrance(); 
  289.  
  290. if ( self::is_temporary_down() ) { 
  291. return $this->get_temporary_unavailable_error(); 
  292.  
  293. $pong = is_null( $unique_anonymous_id ) ? 
  294. Freemius_Api::Ping() : 
  295. $this->_call( 'ping.json?' . http_build_query( array_merge( 
  296. array( 'uid' => $unique_anonymous_id ),  
  297. $params 
  298. ) ) ); 
  299.  
  300. if ( $this->is_valid_ping( $pong ) ) { 
  301. return $pong; 
  302.  
  303. if ( self::should_try_with_http( $pong ) ) { 
  304. // Fallback to HTTP, since HTTPS fails. 
  305. Freemius_Api::SetHttp(); 
  306.  
  307. self::$_options->set_option( 'api_force_http', true, true ); 
  308.  
  309. $pong = is_null( $unique_anonymous_id ) ? 
  310. Freemius_Api::Ping() : 
  311. $this->_call( 'ping.json?' . http_build_query( array_merge( 
  312. array( 'uid' => $unique_anonymous_id ),  
  313. $params 
  314. ) ) ); 
  315.  
  316. if ( ! $this->is_valid_ping( $pong ) ) { 
  317. self::$_options->set_option( 'api_force_http', false, true ); 
  318.  
  319. return $pong; 
  320.  
  321. /** 
  322. * Check if based on the API result we should try 
  323. * to re-run the same request with HTTP instead of HTTPS. 
  324. * @author Vova Feldman (@svovaf) 
  325. * @since 1.1.6 
  326. * @param $result 
  327. * @return bool 
  328. */ 
  329. private static function should_try_with_http( $result ) { 
  330. if ( ! Freemius_Api::IsHttps() ) { 
  331. return false; 
  332.  
  333. return ( ! is_object( $result ) || 
  334. ! isset( $result->error ) || 
  335. ! isset( $result->error->code ) || 
  336. ! in_array( $result->error->code, array( 
  337. 'curl_missing',  
  338. 'cloudflare_ddos_protection',  
  339. 'maintenance_mode',  
  340. 'squid_cache_block',  
  341. 'too_many_requests',  
  342. ) ) ); 
  343.  
  344.  
  345. /** 
  346. * Check if valid ping request result. 
  347. * @author Vova Feldman (@svovaf) 
  348. * @since 1.1.1 
  349. * @param mixed $pong 
  350. * @return bool 
  351. */ 
  352. function is_valid_ping( $pong ) { 
  353. return Freemius_Api::Test( $pong ); 
  354.  
  355. function get_url( $path = '' ) { 
  356. return Freemius_Api::GetUrl( $path, $this->_api->IsSandbox() ); 
  357.  
  358. /** 
  359. * Clear API cache. 
  360. * @author Vova Feldman (@svovaf) 
  361. * @since 1.0.9 
  362. */ 
  363. static function clear_cache() { 
  364. self::_init(); 
  365.  
  366. self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME ); 
  367. self::$_cache->clear( true );