PluginProbe
Tiered Pricing Table for WooCommerce / 8.0.2
Tiered Pricing Table for WooCommerce v8.0.2
8.0.2 7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 2.3.3 All 91 releases
← All changes | freemius/includes/class-fs-api.php +248 -149 2.1.2 → 8.0.2 View file →
@@ -55,9 +55,25 @@
55 55 * @since 1.0.4
56 56 */
57 57 private $_logger;
58 58
59 - /**
59 + /**
60 + * @author Leo Fajardo (@leorw)
61 + * @since 2.3.0
62 + *
63 + * @var string
64 + */
65 + private $_sdk_version;
66 +
67 + /**
68 + * @author Leo Fajardo (@leorw)
69 + * @since 2.5.0
70 + *
71 + * @var string
72 + */
73 + private $_url;
74 +
75 + /**
60 76 * @param string $slug
61 77 * @param string $scope 'app', 'developer', 'user' or 'install'.
62 78 * @param number $id Element's id.
63 79 * @param string $public_key Public key.
@@ -62,18 +78,29 @@
62 78 * @param number $id Element's id.
63 79 * @param string $public_key Public key.
64 80 * @param bool $is_sandbox
65 81 * @param bool|string $secret_key Element's secret key.
82 + * @param null|string $sdk_version
83 + * @param null|string $url
66 84 *
67 85 * @return FS_Api
68 86 */
69 - static function instance( $slug, $scope, $id, $public_key, $is_sandbox, $secret_key = false ) {
87 + static function instance(
88 + $slug,
89 + $scope,
90 + $id,
91 + $public_key,
92 + $is_sandbox,
93 + $secret_key = false,
94 + $sdk_version = null,
95 + $url = null
96 + ) {
70 97 $identifier = md5( $slug . $scope . $id . $public_key . ( is_string( $secret_key ) ? $secret_key : '' ) . json_encode( $is_sandbox ) );
71 98
72 99 if ( ! isset( self::$_instances[ $identifier ] ) ) {
73 100 self::_init();
74 101
75 - self::$_instances[ $identifier ] = new FS_Api( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox );
102 + self::$_instances[ $identifier ] = new FS_Api( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox, $sdk_version, $url );
76 103 }
77 104
78 105 return self::$_instances[ $identifier ];
79 106 }
@@ -104,14 +131,27 @@
104 131 * @param number $id Element's id.
105 132 * @param string $public_key Public key.
106 133 * @param bool|string $secret_key Element's secret key.
107 134 * @param bool $is_sandbox
135 + * @param null|string $sdk_version
136 + * @param null|string $url
108 137 */
109 - private function __construct( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox ) {
138 + private function __construct(
139 + $slug,
140 + $scope,
141 + $id,
142 + $public_key,
143 + $secret_key,
144 + $is_sandbox,
145 + $sdk_version,
146 + $url
147 + ) {
110 148 $this->_api = new Freemius_Api_WordPress( $scope, $id, $public_key, $secret_key, $is_sandbox );
111 149
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 );
150 + $this->_slug = $slug;
151 + $this->_sdk_version = $sdk_version;
152 + $this->_url = $url;
153 + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
114 154 }
115 155
116 156 /**
117 157 * Find clock diff between server and API server, and store the diff locally.
@@ -148,47 +188,95 @@
148 188 *
149 189 * @param string $path
150 190 * @param string $method
151 191 * @param array $params
152 - * @param bool $retry Is in retry or first call attempt.
192 + * @param bool $in_retry Is in retry or first call attempt.
153 193 *
154 194 * @return array|mixed|string|void
155 195 */
156 - private function _call( $path, $method = 'GET', $params = array(), $retry = false ) {
157 - $this->_logger->entrance( $method . ':' . $path );
196 + private function _call( $path, $method = 'GET', $params = array(), $in_retry = false ) {
197 + $this->_logger->entrance( $method . ':' . $path );
158 198
159 - if ( self::is_temporary_down() ) {
160 - $result = $this->get_temporary_unavailable_error();
161 - } else {
162 - $result = $this->_api->Api( $path, $method, $params );
199 + $force_http = ( ! $in_retry && self::$_options->get_option( 'api_force_http', false ) );
163 200
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;
201 + if ( self::is_temporary_down() ) {
202 + $result = $this->get_temporary_unavailable_error();
203 + } else {
204 + /**
205 + * @since 2.3.0 Include the SDK version with all API requests that going through the API manager. IMPORTANT: Only pass the SDK version if the caller didn't include it yet.
206 + */
207 + if ( ! empty( $this->_sdk_version ) ) {
208 + if ( false === strpos( $path, 'sdk_version=' ) &&
209 + ! isset( $params['sdk_version'] )
210 + ) {
211 + // Always add the sdk_version param in the querystring. DO NOT INCLUDE IT IN THE BODY PARAMS, OTHERWISE, IT MAY LEAD TO AN UNEXPECTED PARAMS PARSING IN CASES WHERE THE $params IS A REGULAR NON-ASSOCIATIVE ARRAY.
212 + $path = add_query_arg( 'sdk_version', $this->_sdk_version, $path );
213 + }
214 + }
173 215
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 - }
216 + /**
217 + * @since 2.5.0 Include the site's URL, if available, in all API requests that are going through the API manager.
218 + */
219 + if ( ! empty( $this->_url ) ) {
220 + if ( false === strpos( $path, 'url=' ) &&
221 + ! isset( $params['url'] )
222 + ) {
223 + $path = add_query_arg( 'url', $this->_url, $path );
224 + }
225 + }
182 226
183 - if ( $this->_logger->is_on() && self::is_api_error( $result ) ) {
184 - // Log API errors.
185 - $this->_logger->api_error( $result );
186 - }
227 + $result = $this->_api->Api( $path, $method, $params );
187 228
188 - return $result;
189 - }
229 + if (
230 + ! $in_retry &&
231 + null !== $result &&
232 + isset( $result->error ) &&
233 + isset( $result->error->code )
234 + ) {
235 + $retry = false;
190 236
237 + if ( 'request_expired' === $result->error->code ) {
238 + $diff = isset( $result->error->timestamp ) ?
239 + ( time() - strtotime( $result->error->timestamp ) ) :
240 + false;
241 +
242 + // Try to sync clock diff.
243 + if ( false !== $this->_sync_clock_diff( $diff ) ) {
244 + // Retry call with new synced clock.
245 + $retry = true;
246 + }
247 + } else if (
248 + Freemius_Api_WordPress::IsHttps() &&
249 + FS_Api::is_ssl_error_response( $result )
250 + ) {
251 + $force_http = true;
252 + $retry = true;
253 + }
254 +
255 + if ( $retry ) {
256 + if ( $force_http ) {
257 + $this->toggle_force_http( true );
258 + }
259 +
260 + $result = $this->_call( $path, $method, $params, true );
261 + }
262 + }
263 + }
264 +
265 + if ( self::is_api_error( $result ) ) {
266 + if ( $this->_logger->is_on() ) {
267 + // Log API errors.
268 + $this->_logger->api_error( $result );
269 + }
270 +
271 + if ( $force_http ) {
272 + $this->toggle_force_http( false );
273 + }
274 + }
275 +
276 + return $result;
277 + }
278 +
191 279 /**
192 280 * Override API call to wrap it in servers' clock sync method.
193 281 *
194 282 * @param string $path
@@ -229,11 +317,14 @@
229 317 if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) {
230 318 $flush = true;
231 319 }
232 320
233 - $cached_result = self::$_cache->get( $cache_key );
321 + $has_valid_cache = self::$_cache->has_valid( $cache_key, $expiration );
322 + $cached_result = $has_valid_cache ?
323 + self::$_cache->get( $cache_key ) :
324 + null;
234 325
235 - if ( $flush || ! self::$_cache->has_valid( $cache_key, $expiration ) ) {
326 + if ( $flush || is_null( $cached_result ) ) {
236 327 $result = $this->call( $path );
237 328
238 329 if ( ! is_object( $result ) || isset( $result->error ) ) {
239 330 // Api returned an error.
@@ -247,14 +338,14 @@
247 338 if ( $this->_logger->is_on() ) {
248 339 $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) );
249 340 }
250 341 } else {
251 - if ( is_object( $result ) && 404 == $result->error->http ) {
342 + if ( is_object( $result ) && isset( $result->error->http ) && 404 == $result->error->http ) {
252 343 /**
253 344 * If the response code is 404, cache the result for half of the `$expiration`.
254 345 *
255 346 * @author Leo Fajardo (@leorw)
256 - * @since 2.2.3.1
347 + * @since 2.2.4
257 348 */
258 349 $expiration /= 2;
259 350 } else {
260 351 // If no older data version and the response code is not 404, return result without
@@ -263,9 +354,11 @@
263 354 }
264 355 }
265 356 }
266 357
267 - self::$_cache->set( $cache_key, $result, $expiration );
358 + if ( is_numeric( $expiration ) ) {
359 + self::$_cache->set( $cache_key, $result, $expiration );
360 + }
268 361
269 362 $cached_result = $result;
270 363 } else {
271 364 $this->_logger->log( 'Using cached API result.' );
@@ -273,8 +366,45 @@
273 366
274 367 return $cached_result;
275 368 }
276 369
370 + /**
371 + * @todo Remove this method after migrating Freemius::safe_remote_post() to FS_Api::call().
372 + *
373 + * @author Leo Fajardo (@leorw)
374 + * @since 2.5.4
375 + *
376 + * @param string $url
377 + * @param array $remote_args
378 + *
379 + * @return array|WP_Error The response array or a WP_Error on failure.
380 + */
381 + static function remote_request( $url, $remote_args ) {
382 + if ( ! class_exists( 'Freemius_Api_WordPress' ) ) {
383 + require_once WP_FS__DIR_SDK . '/FreemiusWordPress.php';
384 + }
385 +
386 + if ( method_exists( 'Freemius_Api_WordPress', 'RemoteRequest' ) ) {
387 + return Freemius_Api_WordPress::RemoteRequest( $url, $remote_args );
388 + }
389 +
390 + // The following is for backward compatibility when a modified PHP SDK version is in use and the `Freemius_Api_WordPress:RemoteRequest()` method doesn't exist.
391 + $response = wp_remote_request( $url, $remote_args );
392 +
393 + if (
394 + is_array( $response ) &&
395 + (
396 + empty( $response['headers'] ) ||
397 + empty( $response['headers']['x-api-server'] )
398 + )
399 + ) {
400 + // API is considered blocked if the response doesn't include the `x-api-server` header. When there's no error but this header doesn't exist, the response is usually not in the expected form (e.g., cannot be JSON-decoded).
401 + $response = new WP_Error( 'api_blocked', htmlentities( $response['body'] ) );
402 + }
403 +
404 + return $response;
405 + }
406 +
277 407 /**
278 408 * Check if there's a cached version of the API request.
279 409 *
280 410 * @author Vova Feldman (@svovaf)
@@ -343,53 +473,40 @@
343 473 // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params));
344 474 return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' );
345 475 }
346 476
347 - /**
348 - * Test API connectivity.
349 - *
350 - * @author Vova Feldman (@svovaf)
351 - * @since 1.0.9 If fails, try to fallback to HTTP.
352 - * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if
353 - * temporary down.
354 - *
355 - * @return bool True if successful connectivity to the API.
356 - */
357 - static function test() {
358 - self::_init();
477 + /**
478 + * @author Leo Fajardo (@leorw)
479 + * @since 2.5.4
480 + *
481 + * @param bool $is_http
482 + */
483 + private function toggle_force_http( $is_http ) {
484 + self::$_options->set_option( 'api_force_http', $is_http, true );
359 485
360 - $cache_key = 'ping_test';
486 + if ( $is_http ) {
487 + Freemius_Api_WordPress::SetHttp();
488 + } else if ( method_exists( 'Freemius_Api_WordPress', 'SetHttps' ) ) {
489 + Freemius_Api_WordPress::SetHttps();
490 + }
491 + }
361 492
362 - $test = self::$_cache->get_valid( $cache_key, null );
493 + /**
494 + * @author Leo Fajardo (@leorw)
495 + * @since 2.5.4
496 + *
497 + * @param mixed $response
498 + *
499 + * @return bool
500 + */
501 + static function is_blocked( $response ) {
502 + return (
503 + self::is_api_error_object( $response, true ) &&
504 + isset( $response->error->code ) &&
505 + 'api_blocked' === $response->error->code
506 + );
507 + }
363 508
364 - if ( is_null( $test ) ) {
365 - $test = Freemius_Api_WordPress::Test();
366 -
367 - if ( false === $test && Freemius_Api_WordPress::IsHttps() ) {
368 - // Fallback to HTTP, since HTTPS fails.
369 - Freemius_Api_WordPress::SetHttp();
370 -
371 - self::$_options->set_option( 'api_force_http', true, true );
372 -
373 - $test = Freemius_Api_WordPress::Test();
374 -
375 - if ( false === $test ) {
376 - /**
377 - * API connectivity test fail also in HTTP request, therefore,
378 - * fallback to HTTPS to keep connection secure.
379 - *
380 - * @since 1.1.6
381 - */
382 - self::$_options->set_option( 'api_force_http', false, true );
383 - }
384 - }
385 -
386 - self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC );
387 - }
388 -
389 - return $test;
390 - }
391 -
392 509 /**
393 510 * Check if API is temporary down.
394 511 *
395 512 * @author Vova Feldman (@svovaf)
@@ -422,58 +539,8 @@
422 539 );
423 540 }
424 541
425 542 /**
426 - * Ping API for connectivity test, and return result object.
427 - *
428 - * @author Vova Feldman (@svovaf)
429 - * @since 1.0.9
430 - *
431 - * @param null|string $unique_anonymous_id
432 - * @param array $params
433 - *
434 - * @return object
435 - */
436 - function ping( $unique_anonymous_id = null, $params = array() ) {
437 - $this->_logger->entrance();
438 -
439 - if ( self::is_temporary_down() ) {
440 - return $this->get_temporary_unavailable_error();
441 - }
442 -
443 - $pong = is_null( $unique_anonymous_id ) ?
444 - Freemius_Api_WordPress::Ping() :
445 - $this->_call( 'ping.json?' . http_build_query( array_merge(
446 - array( 'uid' => $unique_anonymous_id ),
447 - $params
448 - ) ) );
449 -
450 - if ( $this->is_valid_ping( $pong ) ) {
451 - return $pong;
452 - }
453 -
454 - if ( self::should_try_with_http( $pong ) ) {
455 - // Fallback to HTTP, since HTTPS fails.
456 - Freemius_Api_WordPress::SetHttp();
457 -
458 - self::$_options->set_option( 'api_force_http', true, true );
459 -
460 - $pong = is_null( $unique_anonymous_id ) ?
461 - Freemius_Api_WordPress::Ping() :
462 - $this->_call( 'ping.json?' . http_build_query( array_merge(
463 - array( 'uid' => $unique_anonymous_id ),
464 - $params
465 - ) ) );
466 -
467 - if ( ! $this->is_valid_ping( $pong ) ) {
468 - self::$_options->set_option( 'api_force_http', false, true );
469 - }
470 - }
471 -
472 - return $pong;
473 - }
474 -
475 - /**
476 543 * Check if based on the API result we should try
477 544 * to re-run the same request with HTTP instead of HTTPS.
478 545 *
479 546 * @author Vova Feldman (@svovaf)
@@ -500,22 +567,8 @@
500 567 ) ) );
501 568
502 569 }
503 570
504 - /**
505 - * Check if valid ping request result.
506 - *
507 - * @author Vova Feldman (@svovaf)
508 - * @since 1.1.1
509 - *
510 - * @param mixed $pong
511 - *
512 - * @return bool
513 - */
514 - function is_valid_ping( $pong ) {
515 - return Freemius_Api_WordPress::Test( $pong );
516 - }
517 -
518 571 function get_url( $path = '' ) {
519 572 return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() );
520 573 }
521 574
@@ -531,8 +584,16 @@
531 584 self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
532 585 self::$_cache->clear();
533 586 }
534 587
588 + /**
589 + * @author Leo Fajardo (@leorw)
590 + * @since 2.5.4
591 + */
592 + static function clear_force_http_flag() {
593 + self::$_options->unset_option( 'api_force_http' );
594 + }
595 +
535 596 #----------------------------------------------------------------------------------
536 597 #region Error Handling
537 598 #----------------------------------------------------------------------------------
538 599
@@ -553,16 +614,54 @@
553 614 * @author Vova Feldman (@svovaf)
554 615 * @since 2.0.0
555 616 *
556 617 * @param mixed $result
618 + * @param bool $ignore_message
557 619 *
558 620 * @return bool Is API result contains an error.
559 621 */
560 - static function is_api_error_object( $result ) {
622 + static function is_api_error_object( $result, $ignore_message = false ) {
561 623 return (
562 624 is_object( $result ) &&
563 625 isset( $result->error ) &&
564 - isset( $result->error->message )
626 + ( $ignore_message || isset( $result->error->message ) )
627 + );
628 + }
629 +
630 + /**
631 + * @author Leo Fajardo (@leorw)
632 + * @since 2.5.4
633 + *
634 + * @param WP_Error|object|string $response
635 + *
636 + * @return bool
637 + */
638 + static function is_ssl_error_response( $response ) {
639 + $http_error = null;
640 +
641 + if ( $response instanceof WP_Error ) {
642 + if (
643 + isset( $response->errors ) &&
644 + isset( $response->errors['http_request_failed'] )
645 + ) {
646 + $http_error = strtolower( $response->errors['http_request_failed'][0] );
647 + }
648 + } else if (
649 + self::is_api_error_object( $response ) &&
650 + ! empty( $response->error->message )
651 + ) {
652 + $http_error = $response->error->message;
653 + }
654 +
655 + return (
656 + ! empty( $http_error ) &&
657 + (
658 + false !== strpos( $http_error, 'curl error 35' ) ||
659 + (
660 + false === strpos( $http_error, '</html>' ) &&
661 + false !== strpos( $http_error, 'ssl' )
662 + )
663 + )
565 664 );
566 665 }
567 666
568 667 /**