PluginProbe
BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites / trunk
BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites vtrunk
4.1.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 2.1.2 2.5.0 2.5.3 2.6.1 All 38 releases
← All changes | freemius/includes/class-fs-api.php +186 -126 1.2.3trunk View file →
@@ -64,8 +64,16 @@
64 64 */
65 65 private $_sdk_version;
66 66
67 67 /**
68 + * @author Leo Fajardo (@leorw)
69 + * @since 2.5.0
70 + *
71 + * @var string
72 + */
73 + private $_url;
74 +
75 + /**
68 76 * @param string $slug
69 77 * @param string $scope 'app', 'developer', 'user' or 'install'.
70 78 * @param number $id Element's id.
71 79 * @param string $public_key Public key.
@@ -71,8 +79,9 @@
71 79 * @param string $public_key Public key.
72 80 * @param bool $is_sandbox
73 81 * @param bool|string $secret_key Element's secret key.
74 82 * @param null|string $sdk_version
83 + * @param null|string $url
75 84 *
76 85 * @return FS_Api
77 86 */
78 87 static function instance(
@@ -81,9 +90,10 @@
81 90 $id,
82 91 $public_key,
83 92 $is_sandbox,
84 93 $secret_key = false,
85 - $sdk_version = null
94 + $sdk_version = null,
95 + $url = null
86 96 ) {
87 97 $identifier = md5( $slug . $scope . $id . $public_key . ( is_string( $secret_key ) ? $secret_key : '' ) . json_encode( $is_sandbox ) );
88 98
89 99 if ( ! isset( self::$_instances[ $identifier ] ) ) {
@@ -88,9 +98,9 @@
88 98
89 99 if ( ! isset( self::$_instances[ $identifier ] ) ) {
90 100 self::_init();
91 101
92 - self::$_instances[ $identifier ] = new FS_Api( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox, $sdk_version );
102 + self::$_instances[ $identifier ] = new FS_Api( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox, $sdk_version, $url );
93 103 }
94 104
95 105 return self::$_instances[ $identifier ];
96 106 }
@@ -122,8 +132,9 @@
122 132 * @param string $public_key Public key.
123 133 * @param bool|string $secret_key Element's secret key.
124 134 * @param bool $is_sandbox
125 135 * @param null|string $sdk_version
136 + * @param null|string $url
126 137 */
127 138 private function __construct(
128 139 $slug,
129 140 $scope,
@@ -130,14 +141,16 @@
130 141 $id,
131 142 $public_key,
132 143 $secret_key,
133 144 $is_sandbox,
134 - $sdk_version
145 + $sdk_version,
146 + $url
135 147 ) {
136 148 $this->_api = new Freemius_Api_WordPress( $scope, $id, $public_key, $secret_key, $is_sandbox );
137 149
138 150 $this->_slug = $slug;
139 151 $this->_sdk_version = $sdk_version;
152 + $this->_url = $url;
140 153 $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
141 154 }
142 155
143 156 /**
@@ -175,15 +188,17 @@
175 188 *
176 189 * @param string $path
177 190 * @param string $method
178 191 * @param array $params
179 - * @param bool $retry Is in retry or first call attempt.
192 + * @param bool $in_retry Is in retry or first call attempt.
180 193 *
181 194 * @return array|mixed|string|void
182 195 */
183 - private function _call( $path, $method = 'GET', $params = array(), $retry = false ) {
196 + private function _call( $path, $method = 'GET', $params = array(), $in_retry = false ) {
184 197 $this->_logger->entrance( $method . ':' . $path );
185 198
199 + $force_http = ( ! $in_retry && self::$_options->get_option( 'api_force_http', false ) );
200 +
186 201 if ( self::is_temporary_down() ) {
187 202 $result = $this->get_temporary_unavailable_error();
188 203 } else {
189 204 /**
@@ -197,16 +212,30 @@
197 212 $path = add_query_arg( 'sdk_version', $this->_sdk_version, $path );
198 213 }
199 214 }
200 215
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 + }
226 +
201 227 $result = $this->_api->Api( $path, $method, $params );
202 228
203 - if ( null !== $result &&
204 - isset( $result->error ) &&
205 - isset( $result->error->code ) &&
206 - 'request_expired' === $result->error->code
229 + if (
230 + ! $in_retry &&
231 + null !== $result &&
232 + isset( $result->error ) &&
233 + isset( $result->error->code )
207 234 ) {
208 - if ( ! $retry ) {
235 + $retry = false;
236 +
237 + if ( 'request_expired' === $result->error->code ) {
209 238 $diff = isset( $result->error->timestamp ) ?
210 239 ( time() - strtotime( $result->error->timestamp ) ) :
211 240 false;
212 241
@@ -212,17 +241,37 @@
212 241
213 242 // Try to sync clock diff.
214 243 if ( false !== $this->_sync_clock_diff( $diff ) ) {
215 244 // Retry call with new synced clock.
216 - return $this->_call( $path, $method, $params, true );
245 + $retry = true;
217 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;
218 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 + }
219 262 }
220 263 }
221 264
222 - if ( $this->_logger->is_on() && self::is_api_error( $result ) ) {
223 - // Log API errors.
224 - $this->_logger->api_error( $result );
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 + }
225 274 }
226 275
227 276 return $result;
228 277 }
@@ -268,11 +317,14 @@
268 317 if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) {
269 318 $flush = true;
270 319 }
271 320
272 - $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;
273 325
274 - if ( $flush || ! self::$_cache->has_valid( $cache_key, $expiration ) ) {
326 + if ( $flush || is_null( $cached_result ) ) {
275 327 $result = $this->call( $path );
276 328
277 329 if ( ! is_object( $result ) || isset( $result->error ) ) {
278 330 // Api returned an error.
@@ -286,9 +338,9 @@
286 338 if ( $this->_logger->is_on() ) {
287 339 $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) );
288 340 }
289 341 } else {
290 - if ( is_object( $result ) && 404 == $result->error->http ) {
342 + if ( is_object( $result ) && isset( $result->error->http ) && 404 == $result->error->http ) {
291 343 /**
292 344 * If the response code is 404, cache the result for half of the `$expiration`.
293 345 *
294 346 * @author Leo Fajardo (@leorw)
@@ -302,9 +354,11 @@
302 354 }
303 355 }
304 356 }
305 357
306 - self::$_cache->set( $cache_key, $result, $expiration );
358 + if ( is_numeric( $expiration ) ) {
359 + self::$_cache->set( $cache_key, $result, $expiration );
360 + }
307 361
308 362 $cached_result = $result;
309 363 } else {
310 364 $this->_logger->log( 'Using cached API result.' );
@@ -312,8 +366,45 @@
312 366
313 367 return $cached_result;
314 368 }
315 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 +
316 407 /**
317 408 * Check if there's a cached version of the API request.
318 409 *
319 410 * @author Vova Feldman (@svovaf)
@@ -382,53 +473,40 @@
382 473 // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params));
383 474 return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' );
384 475 }
385 476
386 - /**
387 - * Test API connectivity.
388 - *
389 - * @author Vova Feldman (@svovaf)
390 - * @since 1.0.9 If fails, try to fallback to HTTP.
391 - * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if
392 - * temporary down.
393 - *
394 - * @return bool True if successful connectivity to the API.
395 - */
396 - static function test() {
397 - 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 );
398 485
399 - $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 + }
400 492
401 - $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 + }
402 508
403 - if ( is_null( $test ) ) {
404 - $test = Freemius_Api_WordPress::Test();
405 -
406 - if ( false === $test && Freemius_Api_WordPress::IsHttps() ) {
407 - // Fallback to HTTP, since HTTPS fails.
408 - Freemius_Api_WordPress::SetHttp();
409 -
410 - self::$_options->set_option( 'api_force_http', true, true );
411 -
412 - $test = Freemius_Api_WordPress::Test();
413 -
414 - if ( false === $test ) {
415 - /**
416 - * API connectivity test fail also in HTTP request, therefore,
417 - * fallback to HTTPS to keep connection secure.
418 - *
419 - * @since 1.1.6
420 - */
421 - self::$_options->set_option( 'api_force_http', false, true );
422 - }
423 - }
424 -
425 - self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC );
426 - }
427 -
428 - return $test;
429 - }
430 -
431 509 /**
432 510 * Check if API is temporary down.
433 511 *
434 512 * @author Vova Feldman (@svovaf)
@@ -461,58 +539,8 @@
461 539 );
462 540 }
463 541
464 542 /**
465 - * Ping API for connectivity test, and return result object.
466 - *
467 - * @author Vova Feldman (@svovaf)
468 - * @since 1.0.9
469 - *
470 - * @param null|string $unique_anonymous_id
471 - * @param array $params
472 - *
473 - * @return object
474 - */
475 - function ping( $unique_anonymous_id = null, $params = array() ) {
476 - $this->_logger->entrance();
477 -
478 - if ( self::is_temporary_down() ) {
479 - return $this->get_temporary_unavailable_error();
480 - }
481 -
482 - $pong = is_null( $unique_anonymous_id ) ?
483 - Freemius_Api_WordPress::Ping() :
484 - $this->_call( 'ping.json?' . http_build_query( array_merge(
485 - array( 'uid' => $unique_anonymous_id ),
486 - $params
487 - ) ) );
488 -
489 - if ( $this->is_valid_ping( $pong ) ) {
490 - return $pong;
491 - }
492 -
493 - if ( self::should_try_with_http( $pong ) ) {
494 - // Fallback to HTTP, since HTTPS fails.
495 - Freemius_Api_WordPress::SetHttp();
496 -
497 - self::$_options->set_option( 'api_force_http', true, true );
498 -
499 - $pong = is_null( $unique_anonymous_id ) ?
500 - Freemius_Api_WordPress::Ping() :
501 - $this->_call( 'ping.json?' . http_build_query( array_merge(
502 - array( 'uid' => $unique_anonymous_id ),
503 - $params
504 - ) ) );
505 -
506 - if ( ! $this->is_valid_ping( $pong ) ) {
507 - self::$_options->set_option( 'api_force_http', false, true );
508 - }
509 - }
510 -
511 - return $pong;
512 - }
513 -
514 - /**
515 543 * Check if based on the API result we should try
516 544 * to re-run the same request with HTTP instead of HTTPS.
517 545 *
518 546 * @author Vova Feldman (@svovaf)
@@ -539,22 +567,8 @@
539 567 ) ) );
540 568
541 569 }
542 570
543 - /**
544 - * Check if valid ping request result.
545 - *
546 - * @author Vova Feldman (@svovaf)
547 - * @since 1.1.1
548 - *
549 - * @param mixed $pong
550 - *
551 - * @return bool
552 - */
553 - function is_valid_ping( $pong ) {
554 - return Freemius_Api_WordPress::Test( $pong );
555 - }
556 -
557 571 function get_url( $path = '' ) {
558 572 return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() );
559 573 }
560 574
@@ -570,8 +584,16 @@
570 584 self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
571 585 self::$_cache->clear();
572 586 }
573 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 +
574 596 #----------------------------------------------------------------------------------
575 597 #region Error Handling
576 598 #----------------------------------------------------------------------------------
577 599
@@ -592,16 +614,54 @@
592 614 * @author Vova Feldman (@svovaf)
593 615 * @since 2.0.0
594 616 *
595 617 * @param mixed $result
618 + * @param bool $ignore_message
596 619 *
597 620 * @return bool Is API result contains an error.
598 621 */
599 - static function is_api_error_object( $result ) {
622 + static function is_api_error_object( $result, $ignore_message = false ) {
600 623 return (
601 624 is_object( $result ) &&
602 625 isset( $result->error ) &&
603 - 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 + )
604 664 );
605 665 }
606 666
607 667 /**