PluginProbe
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder / 3.6.0
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder v3.6.0
3.6.0 3.5.0 trunk 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.6.1 1.0.7 1.0.8 1.0.9 1.1 1.1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5 1.5.1 All 110 releases
← All changes | freemius/includes/class-fs-api.php +260 -151 1.53.6.0 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,17 +317,20 @@
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.
240 331 if ( is_object( $cached_result ) &&
241 - ! isset( $cached_result )
332 + ! isset( $cached_result->error )
242 333 ) {
243 334 // If there was an error during a newer data fetch,
244 335 // fallback to older data version.
245 336 $result = $cached_result;
@@ -247,15 +338,27 @@
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 no older data version, return result without
252 - // caching the error.
253 - return $result;
342 + if ( is_object( $result ) && isset( $result->error->http ) && 404 == $result->error->http ) {
343 + /**
344 + * If the response code is 404, cache the result for half of the `$expiration`.
345 + *
346 + * @author Leo Fajardo (@leorw)
347 + * @since 2.2.4
348 + */
349 + $expiration /= 2;
350 + } else {
351 + // If no older data version and the response code is not 404, return result without
352 + // caching the error.
353 + return $result;
354 + }
254 355 }
255 356 }
256 357
257 - self::$_cache->set( $cache_key, $result, $expiration );
358 + if ( is_numeric( $expiration ) ) {
359 + self::$_cache->set( $cache_key, $result, $expiration );
360 + }
258 361
259 362 $cached_result = $result;
260 363 } else {
261 364 $this->_logger->log( 'Using cached API result.' );
@@ -263,8 +366,45 @@
263 366
264 367 return $cached_result;
265 368 }
266 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 +
267 407 /**
268 408 * Check if there's a cached version of the API request.
269 409 *
270 410 * @author Vova Feldman (@svovaf)
@@ -333,53 +473,40 @@
333 473 // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params));
334 474 return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' );
335 475 }
336 476
337 - /**
338 - * Test API connectivity.
339 - *
340 - * @author Vova Feldman (@svovaf)
341 - * @since 1.0.9 If fails, try to fallback to HTTP.
342 - * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if
343 - * temporary down.
344 - *
345 - * @return bool True if successful connectivity to the API.
346 - */
347 - static function test() {
348 - 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 );
349 485
350 - $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 + }
351 492
352 - $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 + }
353 508
354 - if ( is_null( $test ) ) {
355 - $test = Freemius_Api_WordPress::Test();
356 -
357 - if ( false === $test && Freemius_Api_WordPress::IsHttps() ) {
358 - // Fallback to HTTP, since HTTPS fails.
359 - Freemius_Api_WordPress::SetHttp();
360 -
361 - self::$_options->set_option( 'api_force_http', true, true );
362 -
363 - $test = Freemius_Api_WordPress::Test();
364 -
365 - if ( false === $test ) {
366 - /**
367 - * API connectivity test fail also in HTTP request, therefore,
368 - * fallback to HTTPS to keep connection secure.
369 - *
370 - * @since 1.1.6
371 - */
372 - self::$_options->set_option( 'api_force_http', false, true );
373 - }
374 - }
375 -
376 - self::$_cache->set( $cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC );
377 - }
378 -
379 - return $test;
380 - }
381 -
382 509 /**
383 510 * Check if API is temporary down.
384 511 *
385 512 * @author Vova Feldman (@svovaf)
@@ -412,58 +539,8 @@
412 539 );
413 540 }
414 541
415 542 /**
416 - * Ping API for connectivity test, and return result object.
417 - *
418 - * @author Vova Feldman (@svovaf)
419 - * @since 1.0.9
420 - *
421 - * @param null|string $unique_anonymous_id
422 - * @param array $params
423 - *
424 - * @return object
425 - */
426 - function ping( $unique_anonymous_id = null, $params = array() ) {
427 - $this->_logger->entrance();
428 -
429 - if ( self::is_temporary_down() ) {
430 - return $this->get_temporary_unavailable_error();
431 - }
432 -
433 - $pong = is_null( $unique_anonymous_id ) ?
434 - Freemius_Api_WordPress::Ping() :
435 - $this->_call( 'ping.json?' . http_build_query( array_merge(
436 - array( 'uid' => $unique_anonymous_id ),
437 - $params
438 - ) ) );
439 -
440 - if ( $this->is_valid_ping( $pong ) ) {
441 - return $pong;
442 - }
443 -
444 - if ( self::should_try_with_http( $pong ) ) {
445 - // Fallback to HTTP, since HTTPS fails.
446 - Freemius_Api_WordPress::SetHttp();
447 -
448 - self::$_options->set_option( 'api_force_http', true, true );
449 -
450 - $pong = is_null( $unique_anonymous_id ) ?
451 - Freemius_Api_WordPress::Ping() :
452 - $this->_call( 'ping.json?' . http_build_query( array_merge(
453 - array( 'uid' => $unique_anonymous_id ),
454 - $params
455 - ) ) );
456 -
457 - if ( ! $this->is_valid_ping( $pong ) ) {
458 - self::$_options->set_option( 'api_force_http', false, true );
459 - }
460 - }
461 -
462 - return $pong;
463 - }
464 -
465 - /**
466 543 * Check if based on the API result we should try
467 544 * to re-run the same request with HTTP instead of HTTPS.
468 545 *
469 546 * @author Vova Feldman (@svovaf)
@@ -490,22 +567,8 @@
490 567 ) ) );
491 568
492 569 }
493 570
494 - /**
495 - * Check if valid ping request result.
496 - *
497 - * @author Vova Feldman (@svovaf)
498 - * @since 1.1.1
499 - *
500 - * @param mixed $pong
501 - *
502 - * @return bool
503 - */
504 - function is_valid_ping( $pong ) {
505 - return Freemius_Api_WordPress::Test( $pong );
506 - }
507 -
508 571 function get_url( $path = '' ) {
509 572 return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() );
510 573 }
511 574
@@ -521,8 +584,16 @@
521 584 self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
522 585 self::$_cache->clear();
523 586 }
524 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 +
525 596 #----------------------------------------------------------------------------------
526 597 #region Error Handling
527 598 #----------------------------------------------------------------------------------
528 599
@@ -543,16 +614,54 @@
543 614 * @author Vova Feldman (@svovaf)
544 615 * @since 2.0.0
545 616 *
546 617 * @param mixed $result
618 + * @param bool $ignore_message
547 619 *
548 620 * @return bool Is API result contains an error.
549 621 */
550 - static function is_api_error_object( $result ) {
622 + static function is_api_error_object( $result, $ignore_message = false ) {
551 623 return (
552 624 is_object( $result ) &&
553 625 isset( $result->error ) &&
554 - isset( $result->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 + )
555 664 );
556 665 }
557 666
558 667 /**