Completed
Push — master ( e12db8...fe8184 )
by Ahmad
02:59
created

FS_Api::_call()   D

Complexity

Conditions 9
Paths 9

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 29
rs 4.9091
nc 9
cc 9
eloc 15
nop 4
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 22 and the first side effect is on line 10.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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_Option_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 ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
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
				if ( 0 === count( self::$_instances ) ) {
74
					self::_init();
75
				}
76
77
				self::$_instances[ $identifier ] = new FS_Api( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox );
78
			}
79
80
			return self::$_instances[ $identifier ];
81
		}
82
83
		private static function _init() {
84
			if ( ! class_exists( 'Freemius_Api' ) ) {
85
				require_once( WP_FS__DIR_SDK . '/Freemius.php' );
86
			}
87
88
			self::$_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true );
89
			self::$_cache   = FS_Option_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME, true );
90
91
			self::$_clock_diff = self::$_options->get_option( 'api_clock_diff', 0 );
92
			Freemius_Api::SetClockDiff( self::$_clock_diff );
93
94
			if ( self::$_options->get_option( 'api_force_http', false ) ) {
95
				Freemius_Api::SetHttp();
96
			}
97
		}
98
99
		/**
100
		 * @param string      $slug
101
		 * @param string      $scope      'app', 'developer', 'user' or 'install'.
102
		 * @param number      $id         Element's id.
103
		 * @param string      $public_key Public key.
104
		 * @param bool|string $secret_key Element's secret key.
105
		 * @param bool        $is_sandbox
106
		 */
107
		private function __construct( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox ) {
108
			$this->_api = new Freemius_Api( $scope, $id, $public_key, $secret_key, $is_sandbox );
109
110
			$this->_slug   = $slug;
111
			$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
112
		}
113
114
		/**
115
		 * Find clock diff between server and API server, and store the diff locally.
116
		 *
117
		 * @param bool|int $diff
118
		 *
119
		 * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds.
120
		 */
121
		private function _sync_clock_diff( $diff = false ) {
122
			$this->_logger->entrance();
123
124
			// Sync clock and store.
125
			$new_clock_diff = ( false === $diff ) ?
126
				$this->_api->FindClockDiff() :
127
				$diff;
128
129
			if ( $new_clock_diff === self::$_clock_diff ) {
130
				return false;
131
			}
132
133
			self::$_clock_diff = $new_clock_diff;
134
135
			// Update API clock's diff.
136
			$this->_api->SetClockDiff( self::$_clock_diff );
137
138
			// Store new clock diff in storage.
139
			self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true );
140
141
			return $new_clock_diff;
142
		}
143
144
		/**
145
		 * Override API call to enable retry with servers' clock auto sync method.
146
		 *
147
		 * @param string $path
148
		 * @param string $method
149
		 * @param array  $params
150
		 * @param bool   $retry Is in retry or first call attempt.
151
		 *
152
		 * @return array|mixed|string|void
153
		 */
154
		private function _call( $path, $method = 'GET', $params = array(), $retry = false ) {
155
			$this->_logger->entrance();
156
157
			$result = $this->_api->Api( $path, $method, $params );
158
159
			if ( null !== $result &&
160
			     isset( $result->error ) &&
161
			     'request_expired' === $result->error->code
162
			) {
163
				if ( ! $retry ) {
164
					$diff = isset( $result->error->timestamp ) ?
165
						( time() - strtotime( $result->error->timestamp ) ) :
166
						false;
167
168
					// Try to sync clock diff.
169
					if ( false !== $this->_sync_clock_diff( $diff ) ) {
170
						// Retry call with new synced clock.
171
						return $this->_call( $path, $method, $params, true );
172
					}
173
				}
174
			}
175
176
			if ( null !== $result && isset( $result->error ) ) {
177
				// Log API errors.
178
				$this->_logger->error( $result->error->message );
179
			}
180
181
			return $result;
182
		}
183
184
		/**
185
		 * Override API call to wrap it in servers' clock sync method.
186
		 *
187
		 * @param string $path
188
		 * @param string $method
189
		 * @param array  $params
190
		 *
191
		 * @return array|mixed|string|void
192
		 * @throws Freemius_Exception
193
		 */
194
		function call( $path, $method = 'GET', $params = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
195
			return $this->_call( $path, $method, $params );
196
		}
197
198
		/**
199
		 * Get API request URL signed via query string.
200
		 *
201
		 * @param string $path
202
		 *
203
		 * @return string
204
		 */
205
		function get_signed_url( $path ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
206
			return $this->_api->GetSignedUrl( $path );
207
		}
208
209
		/**
210
		 * @param string $path
211
		 * @param bool   $flush
212
		 * @param int    $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours
213
		 *
214
		 * @return stdClass|mixed
215
		 */
216
		function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
217
			$cache_key = $this->get_cache_key( $path );
218
219
			// Always flush during development.
220
			if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) {
221
				$flush = true;
222
			}
223
224
			// Get result from cache.
225
			$cache_entry = self::$_cache->get_option( $cache_key, false );
226
227
			$fetch = false;
228
			if ( $flush ||
229
			     false === $cache_entry ||
230
			     ! isset( $cache_entry->timestamp ) ||
231
			     ! is_numeric( $cache_entry->timestamp ) ||
232
			     $cache_entry->timestamp < WP_FS__SCRIPT_START_TIME
233
			) {
234
				$fetch = true;
235
			}
236
237
			if ( $fetch ) {
238
				$result = $this->call( $path );
239
240
				if ( ! is_object( $result ) || isset( $result->error ) ) {
241
					if ( is_object( $cache_entry ) &&
242
					     isset( $cache_entry->result ) &&
243
					     ! isset( $cache_entry->result->error )
244
					) {
245
						// If there was an error during a newer data fetch,
246
						// fallback to older data version.
247
						$result = $cache_entry->result;
248
					} else {
249
						// If no older data version, return result without
250
						// caching the error.
251
						return $result;
252
					}
253
				}
254
255
				$cache_entry            = new stdClass();
256
				$cache_entry->result    = $result;
257
				$cache_entry->timestamp = WP_FS__SCRIPT_START_TIME + $expiration;
258
				self::$_cache->set_option( $cache_key, $cache_entry, true );
259
			}
260
261
			return $cache_entry->result;
262
		}
263
264
		private function get_cache_key( $path, $method = 'GET', $params = array() ) {
265
			$canonized = $this->_api->CanonizePath( $path );
266
//			$exploded = explode('/', $canonized);
0 ignored issues
show
Unused Code Comprehensibility introduced by
44% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
267
//			return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params));
268
			return $method . ':' . $canonized . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' );
269
		}
270
271
		/**
272
		 * Test API connectivity.
273
		 *
274
		 * @since  1.0.9 If fails, try to fallback to HTTP.
275
		 *
276
		 * @param null|string $unique_anonymous_id
277
		 *
278
		 * @return bool True if successful connectivity to the API.
279
		 */
280
		function test( $unique_anonymous_id = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
281
			$this->_logger->entrance();
282
283
			if ( ! function_exists( 'curl_version' ) ) {
284
				// cUrl extension is not active.
285
				return false;
286
			}
287
288
			$test = is_null( $unique_anonymous_id ) ?
289
				$this->_api->Test() :
290
				$this->_api->Test( $this->_call( 'ping.json?uid=' . $unique_anonymous_id ) );
291
292
			if ( false === $test && $this->_api->IsHttps() ) {
293
				// Fallback to HTTP, since HTTPS fails.
294
				$this->_api->SetHttp();
295
296
				self::$_options->set_option( 'api_force_http', true, true );
297
298
				$test = is_null( $unique_anonymous_id ) ?
299
					$this->_api->Test() :
300
					$this->_api->Test( $this->_call( 'ping.json?uid=' . $unique_anonymous_id ) );
301
			}
302
303
			return $test;
304
		}
305
306
		/**
307
		 * Ping API for connectivity test, and return result object.
308
		 *
309
		 * @author Vova Feldman (@svovaf)
310
		 * @since  1.0.9
311
		 *
312
		 * @param null|string $unique_anonymous_id
313
		 *
314
		 * @return object
315
		 */
316
		function ping( $unique_anonymous_id = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
317
			return is_null( $unique_anonymous_id ) ?
318
				$this->_api->Ping() :
319
				$this->_call( 'ping.json?uid=' . $unique_anonymous_id );
320
		}
321
322
		/**
323
		 * Check if valid ping request result.
324
		 *
325
		 * @author Vova Feldman (@svovaf)
326
		 * @since  1.1.1
327
		 *
328
		 * @param mixed $pong
329
		 *
330
		 * @return bool
331
		 */
332
		function is_valid_ping( $pong ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
333
			return $this->_api->Test( $pong );
334
		}
335
336
		function get_url( $path = '' ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
337
			return $this->_api->GetUrl( $path );
338
		}
339
340
		/**
341
		 * Clear API cache.
342
		 *
343
		 * @author Vova Feldman (@svovaf)
344
		 * @since  1.0.9
345
		 */
346
		static function clear_cache() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
347
			self::$_cache = FS_Option_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME, true );
348
			self::$_cache->clear( true );
349
		}
350
	}