|
1
|
|
|
<?php |
|
|
|
|
|
|
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 ) { |
|
|
|
|
|
|
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() ) { |
|
|
|
|
|
|
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 ) { |
|
|
|
|
|
|
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 ) { |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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 ) { |
|
|
|
|
|
|
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 ) { |
|
|
|
|
|
|
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 ) { |
|
|
|
|
|
|
333
|
|
|
return $this->_api->Test( $pong ); |
|
334
|
|
|
} |
|
335
|
|
|
|
|
336
|
|
|
function get_url( $path = '' ) { |
|
|
|
|
|
|
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() { |
|
|
|
|
|
|
347
|
|
|
self::$_cache = FS_Option_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME, true ); |
|
348
|
|
|
self::$_cache->clear( true ); |
|
349
|
|
|
} |
|
350
|
|
|
} |
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.