PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 1.7.3
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v1.7.3
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 / class-fs-api.php
embedpress / freemius / includes Last commit date
debug 9 years ago entities 9 years ago managers 9 years ago sdk 9 years ago supplements 9 years ago class-freemius-abstract.php 9 years ago class-freemius.php 9 years ago class-fs-api.php 9 years ago class-fs-logger.php 9 years ago class-fs-plugin-updater.php 9 years ago class-fs-security.php 9 years ago fs-core-functions.php 9 years ago fs-essential-functions.php 9 years ago fs-plugin-info-dialog.php 9 years ago i18n.php 9 years ago index.php 9 years ago l10n.php 9 years ago
class-fs-api.php
558 lines
1 <?php
2 /**
3 * @package Freemius
4 * @copyright Copyright (c) 2015, Freemius, Inc.
5 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
6 * @since 1.0.4
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * Class FS_Api
15 *
16 * Wraps Freemius API SDK to handle:
17 * 1. Clock sync.
18 * 2. Fallback to HTTP when HTTPS fails.
19 * 3. Adds caching layer to GET requests.
20 * 4. Adds consistency for failed requests by using last cached version.
21 */
22 class FS_Api {
23 /**
24 * @var FS_Api[]
25 */
26 private static $_instances = array();
27
28 /**
29 * @var FS_Option_Manager Freemius options, options-manager.
30 */
31 private static $_options;
32
33 /**
34 * @var FS_Cache_Manager API Caching layer
35 */
36 private static $_cache;
37
38 /**
39 * @var int Clock diff in seconds between current server to API server.
40 */
41 private static $_clock_diff;
42
43 /**
44 * @var Freemius_Api
45 */
46 private $_api;
47
48 /**
49 * @var string
50 */
51 private $_slug;
52
53 /**
54 * @var FS_Logger
55 * @since 1.0.4
56 */
57 private $_logger;
58
59 /**
60 * @param string $slug
61 * @param string $scope 'app', 'developer', 'user' or 'install'.
62 * @param number $id Element's id.
63 * @param string $public_key Public key.
64 * @param bool $is_sandbox
65 * @param bool|string $secret_key Element's secret key.
66 *
67 * @return FS_Api
68 */
69 static function instance( $slug, $scope, $id, $public_key, $is_sandbox, $secret_key = false ) {
70 $identifier = md5( $slug . $scope . $id . $public_key . ( is_string( $secret_key ) ? $secret_key : '' ) . json_encode( $is_sandbox ) );
71
72 if ( ! isset( self::$_instances[ $identifier ] ) ) {
73 self::_init();
74
75 self::$_instances[ $identifier ] = new FS_Api( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox );
76 }
77
78 return self::$_instances[ $identifier ];
79 }
80
81 private static function _init() {
82 if ( isset( self::$_options ) ) {
83 return;
84 }
85
86 if ( ! class_exists( 'Freemius_Api' ) ) {
87 require_once WP_FS__DIR_SDK . '/Freemius.php';
88 }
89
90 self::$_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true );
91 self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
92
93 self::$_clock_diff = self::$_options->get_option( 'api_clock_diff', 0 );
94 Freemius_Api::SetClockDiff( self::$_clock_diff );
95
96 if ( self::$_options->get_option( 'api_force_http', false ) ) {
97 Freemius_Api::SetHttp();
98 }
99 }
100
101 /**
102 * @param string $slug
103 * @param string $scope 'app', 'developer', 'user' or 'install'.
104 * @param number $id Element's id.
105 * @param string $public_key Public key.
106 * @param bool|string $secret_key Element's secret key.
107 * @param bool $is_sandbox
108 */
109 private function __construct( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox ) {
110 $this->_api = new Freemius_Api( $scope, $id, $public_key, $secret_key, $is_sandbox );
111
112 $this->_slug = $slug;
113 $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
114 }
115
116 /**
117 * Find clock diff between server and API server, and store the diff locally.
118 *
119 * @param bool|int $diff
120 *
121 * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds.
122 */
123 private function _sync_clock_diff( $diff = false ) {
124 $this->_logger->entrance();
125
126 // Sync clock and store.
127 $new_clock_diff = ( false === $diff ) ?
128 Freemius_Api::FindClockDiff() :
129 $diff;
130
131 if ( $new_clock_diff === self::$_clock_diff ) {
132 return false;
133 }
134
135 self::$_clock_diff = $new_clock_diff;
136
137 // Update API clock's diff.
138 Freemius_Api::SetClockDiff( self::$_clock_diff );
139
140 // Store new clock diff in storage.
141 self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true );
142
143 return $new_clock_diff;
144 }
145
146 /**
147 * Override API call to enable retry with servers' clock auto sync method.
148 *
149 * @param string $path
150 * @param string $method
151 * @param array $params
152 * @param bool $retry Is in retry or first call attempt.
153 *
154 * @return array|mixed|string|void
155 */
156 private function _call( $path, $method = 'GET', $params = array(), $retry = false ) {
157 $this->_logger->entrance( $method . ':' . $path );
158
159 if ( self::is_temporary_down() ) {
160 $result = $this->get_temporary_unavailable_error();
161 } else {
162 $result = $this->_api->Api( $path, $method, $params );
163
164 if ( null !== $result &&
165 isset( $result->error ) &&
166 isset( $result->error->code ) &&
167 'request_expired' === $result->error->code
168 ) {
169 if ( ! $retry ) {
170 $diff = isset( $result->error->timestamp ) ?
171 ( time() - strtotime( $result->error->timestamp ) ) :
172 false;
173
174 // Try to sync clock diff.
175 if ( false !== $this->_sync_clock_diff( $diff ) ) {
176 // Retry call with new synced clock.
177 return $this->_call( $path, $method, $params, true );
178 }
179 }
180 }
181 }
182
183 if ( $this->_logger->is_on() && self::is_api_error( $result ) ) {
184 // Log API errors.
185 $this->_logger->api_error( $result );
186 }
187
188 return $result;
189 }
190
191 /**
192 * Override API call to wrap it in servers' clock sync method.
193 *
194 * @param string $path
195 * @param string $method
196 * @param array $params
197 *
198 * @return array|mixed|string|void
199 * @throws Freemius_Exception
200 */
201 function call( $path, $method = 'GET', $params = array() ) {
202 return $this->_call( $path, $method, $params );
203 }
204
205 /**
206 * Get API request URL signed via query string.
207 *
208 * @param string $path
209 *
210 * @return string
211 */
212 function get_signed_url( $path ) {
213 return $this->_api->GetSignedUrl( $path );
214 }
215
216 /**
217 * @param string $path
218 * @param bool $flush
219 * @param int $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours
220 *
221 * @return stdClass|mixed
222 */
223 function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
224 $this->_logger->entrance( $path );
225
226 $cache_key = $this->get_cache_key( $path );
227
228 // Always flush during development.
229 if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) {
230 $flush = true;
231 }
232
233 $cached_result = self::$_cache->get( $cache_key );
234
235 if ( $flush || ! self::$_cache->has_valid( $cache_key ) ) {
236 $result = $this->call( $path );
237
238 if ( ! is_object( $result ) || isset( $result->error ) ) {
239 // Api returned an error.
240 if ( is_object( $cached_result ) &&
241 ! isset( $cached_result )
242 ) {
243 // If there was an error during a newer data fetch,
244 // fallback to older data version.
245 $result = $cached_result;
246
247 if ( $this->_logger->is_on() ) {
248 $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) );
249 }
250 } else {
251 // If no older data version, return result without
252 // caching the error.
253 return $result;
254 }
255 }
256
257 self::$_cache->set( $cache_key, $result, $expiration );
258
259 $cached_result = $result;
260 } else {
261 $this->_logger->log( 'Using cached API result.' );
262 }
263
264 return $cached_result;
265 }
266
267 /**
268 * Check if there's a cached version of the API request.
269 *
270 * @author Vova Feldman (@svovaf)
271 * @since 1.2.1
272 *
273 * @param string $path
274 * @param string $method
275 * @param array $params
276 *
277 * @return bool
278 */
279 function is_cached( $path, $method = 'GET', $params = array() ) {
280 $cache_key = $this->get_cache_key( $path, $method, $params );
281
282 return self::$_cache->has_valid( $cache_key );
283 }
284
285 /**
286 * Invalidate a cached version of the API request.
287 *
288 * @author Vova Feldman (@svovaf)
289 * @since 1.2.1.5
290 *
291 * @param string $path
292 * @param string $method
293 * @param array $params
294 */
295 function purge_cache( $path, $method = 'GET', $params = array() ) {
296 $this->_logger->entrance( "{$method}:{$path}" );
297
298 $cache_key = $this->get_cache_key( $path, $method, $params );
299
300 self::$_cache->purge( $cache_key );
301 }
302
303 /**
304 * @param string $path
305 * @param string $method
306 * @param array $params
307 *
308 * @return string
309 * @throws \Freemius_Exception
310 */
311 private function get_cache_key( $path, $method = 'GET', $params = array() ) {
312 $canonized = $this->_api->CanonizePath( $path );
313 // $exploded = explode('/', $canonized);
314 // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params));
315 return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' );
316 }
317
318 /**
319 * Test API connectivity.
320 *
321 * @author Vova Feldman (@svovaf)
322 * @since 1.0.9 If fails, try to fallback to HTTP.
323 * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if
324 * temporary down.
325 *
326 * @return bool True if successful connectivity to the API.
327 */
328 static function test() {
329 self::_init();
330
331 $cache_key = 'ping_test';
332
333 $test = self::$_cache->get_valid( $cache_key, null );
334
335 if ( is_null( $test ) ) {
336 $test = Freemius_Api::Test();
337
338 if ( false === $test && Freemius_Api::IsHttps() ) {
339 // Fallback to HTTP, since HTTPS fails.
340 Freemius_Api::SetHttp();
341
342 self::$_options->set_option( 'api_force_http', true, true );
343
344 $test = Freemius_Api::Test();
345
346 if ( false === $test ) {
347 /**
348 * API connectivity test fail also in HTTP request, therefore,
349 * fallback to HTTPS to keep connection secure.
350 *
351 * @since 1.1.6
352 */
353 self::$_options->set_option( 'api_force_http', false, true );
354 }
355 }
356
357 self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC );
358 }
359
360 return $test;
361 }
362
363 /**
364 * Check if API is temporary down.
365 *
366 * @author Vova Feldman (@svovaf)
367 * @since 1.1.6
368 *
369 * @return bool
370 */
371 static function is_temporary_down() {
372 self::_init();
373
374 $test = self::$_cache->get_valid( 'ping_test', null );
375
376 return ( false === $test );
377 }
378
379 /**
380 * @author Vova Feldman (@svovaf)
381 * @since 1.1.6
382 *
383 * @return object
384 */
385 private function get_temporary_unavailable_error() {
386 return (object) array(
387 'error' => (object) array(
388 'type' => 'TemporaryUnavailable',
389 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.',
390 'code' => 'temporary_unavailable',
391 'http' => 503
392 )
393 );
394 }
395
396 /**
397 * Ping API for connectivity test, and return result object.
398 *
399 * @author Vova Feldman (@svovaf)
400 * @since 1.0.9
401 *
402 * @param null|string $unique_anonymous_id
403 * @param array $params
404 *
405 * @return object
406 */
407 function ping( $unique_anonymous_id = null, $params = array() ) {
408 $this->_logger->entrance();
409
410 if ( self::is_temporary_down() ) {
411 return $this->get_temporary_unavailable_error();
412 }
413
414 $pong = is_null( $unique_anonymous_id ) ?
415 Freemius_Api::Ping() :
416 $this->_call( 'ping.json?' . http_build_query( array_merge(
417 array( 'uid' => $unique_anonymous_id ),
418 $params
419 ) ) );
420
421 if ( $this->is_valid_ping( $pong ) ) {
422 return $pong;
423 }
424
425 if ( self::should_try_with_http( $pong ) ) {
426 // Fallback to HTTP, since HTTPS fails.
427 Freemius_Api::SetHttp();
428
429 self::$_options->set_option( 'api_force_http', true, true );
430
431 $pong = is_null( $unique_anonymous_id ) ?
432 Freemius_Api::Ping() :
433 $this->_call( 'ping.json?' . http_build_query( array_merge(
434 array( 'uid' => $unique_anonymous_id ),
435 $params
436 ) ) );
437
438 if ( ! $this->is_valid_ping( $pong ) ) {
439 self::$_options->set_option( 'api_force_http', false, true );
440 }
441 }
442
443 return $pong;
444 }
445
446 /**
447 * Check if based on the API result we should try
448 * to re-run the same request with HTTP instead of HTTPS.
449 *
450 * @author Vova Feldman (@svovaf)
451 * @since 1.1.6
452 *
453 * @param $result
454 *
455 * @return bool
456 */
457 private static function should_try_with_http( $result ) {
458 if ( ! Freemius_Api::IsHttps() ) {
459 return false;
460 }
461
462 return ( ! is_object( $result ) ||
463 ! isset( $result->error ) ||
464 ! isset( $result->error->code ) ||
465 ! in_array( $result->error->code, array(
466 'curl_missing',
467 'cloudflare_ddos_protection',
468 'maintenance_mode',
469 'squid_cache_block',
470 'too_many_requests',
471 ) ) );
472
473 }
474
475 /**
476 * Check if valid ping request result.
477 *
478 * @author Vova Feldman (@svovaf)
479 * @since 1.1.1
480 *
481 * @param mixed $pong
482 *
483 * @return bool
484 */
485 function is_valid_ping( $pong ) {
486 return Freemius_Api::Test( $pong );
487 }
488
489 function get_url( $path = '' ) {
490 return Freemius_Api::GetUrl( $path, $this->_api->IsSandbox() );
491 }
492
493 /**
494 * Clear API cache.
495 *
496 * @author Vova Feldman (@svovaf)
497 * @since 1.0.9
498 */
499 static function clear_cache() {
500 self::_init();
501
502 self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
503 self::$_cache->clear();
504 }
505
506 #----------------------------------------------------------------------------------
507 #region Error Handling
508 #----------------------------------------------------------------------------------
509
510 /**
511 * @author Vova Feldman (@svovaf)
512 * @since 1.2.1.5
513 *
514 * @param mixed $result
515 *
516 * @return bool Is API result contains an error.
517 */
518 static function is_api_error( $result ) {
519 return ( is_object( $result ) && isset( $result->error ) ) ||
520 is_string( $result );
521 }
522
523 /**
524 * Checks if given API result is a non-empty and not an error object.
525 *
526 * @author Vova Feldman (@svovaf)
527 * @since 1.2.1.5
528 *
529 * @param mixed $result
530 * @param string|null $required_property Optional property we want to verify that is set.
531 *
532 * @return bool
533 */
534 static function is_api_result_object( $result, $required_property = null ) {
535 return (
536 is_object( $result ) &&
537 ! isset( $result->error ) &&
538 ( empty( $required_property ) || isset( $result->{$required_property} ) )
539 );
540 }
541
542 /**
543 * Checks if given API result is a non-empty entity object with non-empty ID.
544 *
545 * @author Vova Feldman (@svovaf)
546 * @since 1.2.1.5
547 *
548 * @param mixed $result
549 *
550 * @return bool
551 */
552 static function is_api_result_entity( $result ) {
553 return self::is_api_result_object( $result, 'id' ) &&
554 FS_Entity::is_valid_id( $result->id );
555 }
556
557 #endregion
558 }