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 +317 -151 1.4.43.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 }
@@ -86,9 +113,9 @@
86 113 if ( ! class_exists( 'Freemius_Api_WordPress' ) ) {
87 114 require_once WP_FS__DIR_SDK . '/FreemiusWordPress.php';
88 115 }
89 116
90 - self::$_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true );
117 + self::$_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true, true );
91 118 self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
92 119
93 120 self::$_clock_diff = self::$_options->get_option( 'api_clock_diff', 0 );
94 121 Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff );
@@ -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)
@@ -299,9 +439,28 @@
299 439
300 440 self::$_cache->purge( $cache_key );
301 441 }
302 442
303 - /**
443 + /**
444 + * Invalidate a cached version of the API request.
445 + *
446 + * @author Vova Feldman (@svovaf)
447 + * @since 2.0.0
448 + *
449 + * @param string $path
450 + * @param int $expiration
451 + * @param string $method
452 + * @param array $params
453 + */
454 + function update_cache_expiration( $path, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $method = 'GET', $params = array() ) {
455 + $this->_logger->entrance( "{$method}:{$path}:{$expiration}" );
456 +
457 + $cache_key = $this->get_cache_key( $path, $method, $params );
458 +
459 + self::$_cache->update_expiration( $cache_key, $expiration );
460 + }
461 +
462 + /**
304 463 * @param string $path
305 464 * @param string $method
306 465 * @param array $params
307 466 *
@@ -314,53 +473,40 @@
314 473 // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params));
315 474 return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' );
316 475 }
317 476
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();
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 );
330 485
331 - $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 + }
332 492
333 - $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 + }
334 508
335 - if ( is_null( $test ) ) {
336 - $test = Freemius_Api_WordPress::Test();
337 -
338 - if ( false === $test && Freemius_Api_WordPress::IsHttps() ) {
339 - // Fallback to HTTP, since HTTPS fails.
340 - Freemius_Api_WordPress::SetHttp();
341 -
342 - self::$_options->set_option( 'api_force_http', true, true );
343 -
344 - $test = Freemius_Api_WordPress::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 509 /**
364 510 * Check if API is temporary down.
365 511 *
366 512 * @author Vova Feldman (@svovaf)
@@ -393,58 +539,8 @@
393 539 );
394 540 }
395 541
396 542 /**
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_WordPress::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_WordPress::SetHttp();
428 -
429 - self::$_options->set_option( 'api_force_http', true, true );
430 -
431 - $pong = is_null( $unique_anonymous_id ) ?
432 - Freemius_Api_WordPress::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 543 * Check if based on the API result we should try
448 544 * to re-run the same request with HTTP instead of HTTPS.
449 545 *
450 546 * @author Vova Feldman (@svovaf)
@@ -471,22 +567,8 @@
471 567 ) ) );
472 568
473 569 }
474 570
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_WordPress::Test( $pong );
487 - }
488 -
489 571 function get_url( $path = '' ) {
490 572 return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() );
491 573 }
492 574
@@ -502,8 +584,16 @@
502 584 self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
503 585 self::$_cache->clear();
504 586 }
505 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 +
506 596 #----------------------------------------------------------------------------------
507 597 #region Error Handling
508 598 #----------------------------------------------------------------------------------
509 599
@@ -519,8 +609,62 @@
519 609 return ( is_object( $result ) && isset( $result->error ) ) ||
520 610 is_string( $result );
521 611 }
522 612
613 + /**
614 + * @author Vova Feldman (@svovaf)
615 + * @since 2.0.0
616 + *
617 + * @param mixed $result
618 + * @param bool $ignore_message
619 + *
620 + * @return bool Is API result contains an error.
621 + */
622 + static function is_api_error_object( $result, $ignore_message = false ) {
623 + return (
624 + is_object( $result ) &&
625 + isset( $result->error ) &&
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 + )
664 + );
665 + }
666 +
523 667 /**
524 668 * Checks if given API result is a non-empty and not an error object.
525 669 *
526 670 * @author Vova Feldman (@svovaf)
@@ -552,7 +696,29 @@
552 696 static function is_api_result_entity( $result ) {
553 697 return self::is_api_result_object( $result, 'id' ) &&
554 698 FS_Entity::is_valid_id( $result->id );
555 699 }
700 +
701 + /**
702 + * Get API result error code. If failed to get code, returns an empty string.
703 + *
704 + * @author Vova Feldman (@svovaf)
705 + * @since 2.0.0
706 + *
707 + * @param mixed $result
708 + *
709 + * @return string
710 + */
711 + static function get_error_code( $result ) {
712 + if ( is_object( $result ) &&
713 + isset( $result->error ) &&
714 + is_object( $result->error ) &&
715 + ! empty( $result->error->code )
716 + ) {
717 + return $result->error->code;
718 + }
719 +
720 + return '';
721 + }
556 722
557 723 #endregion
558 724 }