Simple Sitemap – Automatically Generate a Responsive Sitemap - Version 3.5.5

Version Description

  • March 4, 2022 =

  • Security update. Fixes minor issues in the Freemius SDK (that handle licensing and plugin updates).

Download this release

Release Info

Developer dgwyer
Plugin Icon Simple Sitemap – Automatically Generate a Responsive Sitemap
Version 3.5.5
Comparing to
See all releases

Code changes from version 3.5.4 to 3.5.5

Files changed (49) hide show
  1. freemius/includes/class-freemius.php +26 -3
  2. freemius/includes/class-fs-api.php +663 -663
  3. freemius/includes/class-fs-options.php +430 -430
  4. freemius/includes/class-fs-plugin-updater.php +1561 -1561
  5. freemius/includes/class-fs-storage.php +531 -531
  6. freemius/includes/fs-core-functions.php +1415 -1415
  7. freemius/includes/fs-essential-functions.php +499 -499
  8. freemius/includes/fs-plugin-info-dialog.php +1644 -1644
  9. freemius/includes/i18n.php +605 -605
  10. freemius/includes/managers/class-fs-admin-notice-manager.php +7 -2
  11. freemius/includes/managers/class-fs-plugin-manager.php +219 -219
  12. freemius/includes/sdk/Exceptions/ArgumentNotExistException.php +5 -1
  13. freemius/includes/sdk/Exceptions/EmptyArgumentException.php +5 -1
  14. freemius/includes/sdk/Exceptions/Exception.php +5 -1
  15. freemius/includes/sdk/Exceptions/InvalidArgumentException.php +5 -1
  16. freemius/includes/sdk/Exceptions/OAuthException.php +5 -1
  17. freemius/includes/sdk/FreemiusBase.php +4 -0
  18. freemius/includes/sdk/FreemiusWordPress.php +4 -1
  19. freemius/languages/freemius-cs_CZ.mo +0 -0
  20. freemius/languages/freemius-da_DK.mo +0 -0
  21. freemius/languages/freemius-en.mo +0 -0
  22. freemius/languages/freemius-es_ES.mo +0 -0
  23. freemius/languages/freemius-fr_FR.mo +0 -0
  24. freemius/languages/freemius-he_IL.mo +0 -0
  25. freemius/languages/freemius-hu_HU.mo +0 -0
  26. freemius/languages/freemius-it_IT.mo +0 -0
  27. freemius/languages/freemius-ja.mo +0 -0
  28. freemius/languages/freemius-nl_NL.mo +0 -0
  29. freemius/languages/freemius-ru_RU.mo +0 -0
  30. freemius/languages/freemius-ta.mo +0 -0
  31. freemius/languages/freemius-zh_CN.mo +0 -0
  32. freemius/languages/freemius.pot +250 -258
  33. freemius/require.php +5 -1
  34. freemius/start.php +1 -1
  35. freemius/templates/account/partials/addon.php +6 -1
  36. freemius/templates/account/partials/site.php +351 -351
  37. freemius/templates/add-ons.php +501 -501
  38. freemius/templates/ajax-loader.php +6 -1
  39. freemius/templates/debug.php +8 -2
  40. freemius/templates/firewall-issues-js.php +10 -6
  41. freemius/templates/forms/data-debug-mode.php +212 -212
  42. freemius/templates/forms/optout.php +336 -336
  43. freemius/templates/forms/user-change.php +296 -296
  44. freemius/templates/partials/network-activation.php +6 -1
  45. freemius/templates/sticky-admin-notice-js.php +4 -2
  46. freemius/templates/tabs.php +189 -189
  47. lib/src/blocks/extend-blocks.js +2 -2
  48. readme.txt +5 -1
  49. simple-sitemap.php +1 -1
freemius/includes/class-freemius.php CHANGED
@@ -3550,6 +3550,8 @@
3550
  * @since 1.1.7.3
3551
  */
3552
  static function _toggle_debug_mode() {
 
 
3553
  if ( ! is_super_admin() ) {
3554
  return;
3555
  }
@@ -3571,10 +3573,19 @@
3571
  * @since 1.2.1.6
3572
  */
3573
  static function _get_debug_log() {
 
 
 
 
 
 
 
 
 
3574
  $logs = FS_Logger::load_db_logs(
3575
  fs_request_get( 'filters', false, 'post' ),
3576
- ! empty( $_POST['limit'] ) && is_numeric( $_POST['limit'] ) ? $_POST['limit'] : 200,
3577
- ! empty( $_POST['offset'] ) && is_numeric( $_POST['offset'] ) ? $_POST['offset'] : 0
3578
  );
3579
 
3580
  self::shoot_ajax_success( $logs );
@@ -4047,7 +4058,7 @@
4047
  if ( empty( $unique_id ) || ! is_string( $unique_id ) ) {
4048
  $key = fs_strip_url_protocol( get_site_url( $blog_id ) );
4049
 
4050
- $secure_auth = SECURE_AUTH_KEY;
4051
  if ( empty( $secure_auth ) ||
4052
  false !== strpos( $secure_auth, ' ' ) ||
4053
  'put your unique phrase here' === $secure_auth
@@ -4447,6 +4458,12 @@
4447
  * @since 1.0.9
4448
  */
4449
  function _email_about_firewall_issue() {
 
 
 
 
 
 
4450
  $this->_admin_notices->remove_sticky( 'failed_connect_api' );
4451
 
4452
  $pong = $this->ping();
@@ -4521,6 +4538,12 @@
4521
  * @since 1.1.7.4
4522
  */
4523
  function _retry_connectivity_test() {
 
 
 
 
 
 
4524
  $this->_admin_notices->remove_sticky( 'failed_connect_api_first' );
4525
 
4526
  $pong = $this->ping();
3550
  * @since 1.1.7.3
3551
  */
3552
  static function _toggle_debug_mode() {
3553
+ check_admin_referer( 'fs_toggle_debug_mode' );
3554
+
3555
  if ( ! is_super_admin() ) {
3556
  return;
3557
  }
3573
  * @since 1.2.1.6
3574
  */
3575
  static function _get_debug_log() {
3576
+ check_admin_referer( 'fs_get_debug_log' );
3577
+
3578
+ if ( ! is_super_admin() ) {
3579
+ return;
3580
+ }
3581
+
3582
+ $limit = min( ! empty( $_POST['limit'] ) ? absint( $_POST['limit'] ) : 200, 200 );
3583
+ $offset = min( ! empty( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 200, 200 );
3584
+
3585
  $logs = FS_Logger::load_db_logs(
3586
  fs_request_get( 'filters', false, 'post' ),
3587
+ $limit,
3588
+ $offset
3589
  );
3590
 
3591
  self::shoot_ajax_success( $logs );
4058
  if ( empty( $unique_id ) || ! is_string( $unique_id ) ) {
4059
  $key = fs_strip_url_protocol( get_site_url( $blog_id ) );
4060
 
4061
+ $secure_auth = defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : '';
4062
  if ( empty( $secure_auth ) ||
4063
  false !== strpos( $secure_auth, ' ' ) ||
4064
  'put your unique phrase here' === $secure_auth
4458
  * @since 1.0.9
4459
  */
4460
  function _email_about_firewall_issue() {
4461
+ check_admin_referer( 'fs_resolve_firewall_issues' );
4462
+
4463
+ if ( ! current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) {
4464
+ return;
4465
+ }
4466
+
4467
  $this->_admin_notices->remove_sticky( 'failed_connect_api' );
4468
 
4469
  $pong = $this->ping();
4538
  * @since 1.1.7.4
4539
  */
4540
  function _retry_connectivity_test() {
4541
+ check_admin_referer( 'fs_retry_connectivity_test' );
4542
+
4543
+ if ( ! current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) {
4544
+ return;
4545
+ }
4546
+
4547
  $this->_admin_notices->remove_sticky( 'failed_connect_api_first' );
4548
 
4549
  $pong = $this->ping();
freemius/includes/class-fs-api.php CHANGED
@@ -1,664 +1,664 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
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_Cache_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_WordPress
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
- * @author Leo Fajardo (@leorw)
61
- * @since 2.3.0
62
- *
63
- * @var string
64
- */
65
- private $_sdk_version;
66
-
67
- /**
68
- * @param string $slug
69
- * @param string $scope 'app', 'developer', 'user' or 'install'.
70
- * @param number $id Element's id.
71
- * @param string $public_key Public key.
72
- * @param bool $is_sandbox
73
- * @param bool|string $secret_key Element's secret key.
74
- * @param null|string $sdk_version
75
- *
76
- * @return FS_Api
77
- */
78
- static function instance(
79
- $slug,
80
- $scope,
81
- $id,
82
- $public_key,
83
- $is_sandbox,
84
- $secret_key = false,
85
- $sdk_version = null
86
- ) {
87
- $identifier = md5( $slug . $scope . $id . $public_key . ( is_string( $secret_key ) ? $secret_key : '' ) . json_encode( $is_sandbox ) );
88
-
89
- if ( ! isset( self::$_instances[ $identifier ] ) ) {
90
- self::_init();
91
-
92
- self::$_instances[ $identifier ] = new FS_Api( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox, $sdk_version );
93
- }
94
-
95
- return self::$_instances[ $identifier ];
96
- }
97
-
98
- private static function _init() {
99
- if ( isset( self::$_options ) ) {
100
- return;
101
- }
102
-
103
- if ( ! class_exists( 'Freemius_Api_WordPress' ) ) {
104
- require_once WP_FS__DIR_SDK . '/FreemiusWordPress.php';
105
- }
106
-
107
- self::$_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true, true );
108
- self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
109
-
110
- self::$_clock_diff = self::$_options->get_option( 'api_clock_diff', 0 );
111
- Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff );
112
-
113
- if ( self::$_options->get_option( 'api_force_http', false ) ) {
114
- Freemius_Api_WordPress::SetHttp();
115
- }
116
- }
117
-
118
- /**
119
- * @param string $slug
120
- * @param string $scope 'app', 'developer', 'user' or 'install'.
121
- * @param number $id Element's id.
122
- * @param string $public_key Public key.
123
- * @param bool|string $secret_key Element's secret key.
124
- * @param bool $is_sandbox
125
- * @param null|string $sdk_version
126
- */
127
- private function __construct(
128
- $slug,
129
- $scope,
130
- $id,
131
- $public_key,
132
- $secret_key,
133
- $is_sandbox,
134
- $sdk_version
135
- ) {
136
- $this->_api = new Freemius_Api_WordPress( $scope, $id, $public_key, $secret_key, $is_sandbox );
137
-
138
- $this->_slug = $slug;
139
- $this->_sdk_version = $sdk_version;
140
- $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
141
- }
142
-
143
- /**
144
- * Find clock diff between server and API server, and store the diff locally.
145
- *
146
- * @param bool|int $diff
147
- *
148
- * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds.
149
- */
150
- private function _sync_clock_diff( $diff = false ) {
151
- $this->_logger->entrance();
152
-
153
- // Sync clock and store.
154
- $new_clock_diff = ( false === $diff ) ?
155
- Freemius_Api_WordPress::FindClockDiff() :
156
- $diff;
157
-
158
- if ( $new_clock_diff === self::$_clock_diff ) {
159
- return false;
160
- }
161
-
162
- self::$_clock_diff = $new_clock_diff;
163
-
164
- // Update API clock's diff.
165
- Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff );
166
-
167
- // Store new clock diff in storage.
168
- self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true );
169
-
170
- return $new_clock_diff;
171
- }
172
-
173
- /**
174
- * Override API call to enable retry with servers' clock auto sync method.
175
- *
176
- * @param string $path
177
- * @param string $method
178
- * @param array $params
179
- * @param bool $retry Is in retry or first call attempt.
180
- *
181
- * @return array|mixed|string|void
182
- */
183
- private function _call( $path, $method = 'GET', $params = array(), $retry = false ) {
184
- $this->_logger->entrance( $method . ':' . $path );
185
-
186
- if ( self::is_temporary_down() ) {
187
- $result = $this->get_temporary_unavailable_error();
188
- } else {
189
- /**
190
- * @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.
191
- */
192
- if ( ! empty( $this->_sdk_version ) ) {
193
- if ( false === strpos( $path, 'sdk_version=' ) &&
194
- ! isset( $params['sdk_version'] )
195
- ) {
196
- // 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.
197
- $path = add_query_arg( 'sdk_version', $this->_sdk_version, $path );
198
- }
199
- }
200
-
201
- $result = $this->_api->Api( $path, $method, $params );
202
-
203
- if ( null !== $result &&
204
- isset( $result->error ) &&
205
- isset( $result->error->code ) &&
206
- 'request_expired' === $result->error->code
207
- ) {
208
- if ( ! $retry ) {
209
- $diff = isset( $result->error->timestamp ) ?
210
- ( time() - strtotime( $result->error->timestamp ) ) :
211
- false;
212
-
213
- // Try to sync clock diff.
214
- if ( false !== $this->_sync_clock_diff( $diff ) ) {
215
- // Retry call with new synced clock.
216
- return $this->_call( $path, $method, $params, true );
217
- }
218
- }
219
- }
220
- }
221
-
222
- if ( $this->_logger->is_on() && self::is_api_error( $result ) ) {
223
- // Log API errors.
224
- $this->_logger->api_error( $result );
225
- }
226
-
227
- return $result;
228
- }
229
-
230
- /**
231
- * Override API call to wrap it in servers' clock sync method.
232
- *
233
- * @param string $path
234
- * @param string $method
235
- * @param array $params
236
- *
237
- * @return array|mixed|string|void
238
- * @throws Freemius_Exception
239
- */
240
- function call( $path, $method = 'GET', $params = array() ) {
241
- return $this->_call( $path, $method, $params );
242
- }
243
-
244
- /**
245
- * Get API request URL signed via query string.
246
- *
247
- * @param string $path
248
- *
249
- * @return string
250
- */
251
- function get_signed_url( $path ) {
252
- return $this->_api->GetSignedUrl( $path );
253
- }
254
-
255
- /**
256
- * @param string $path
257
- * @param bool $flush
258
- * @param int $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours
259
- *
260
- * @return stdClass|mixed
261
- */
262
- function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
263
- $this->_logger->entrance( $path );
264
-
265
- $cache_key = $this->get_cache_key( $path );
266
-
267
- // Always flush during development.
268
- if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) {
269
- $flush = true;
270
- }
271
-
272
- $cached_result = self::$_cache->get( $cache_key );
273
-
274
- if ( $flush || ! self::$_cache->has_valid( $cache_key, $expiration ) ) {
275
- $result = $this->call( $path );
276
-
277
- if ( ! is_object( $result ) || isset( $result->error ) ) {
278
- // Api returned an error.
279
- if ( is_object( $cached_result ) &&
280
- ! isset( $cached_result->error )
281
- ) {
282
- // If there was an error during a newer data fetch,
283
- // fallback to older data version.
284
- $result = $cached_result;
285
-
286
- if ( $this->_logger->is_on() ) {
287
- $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) );
288
- }
289
- } else {
290
- if ( is_object( $result ) && isset( $result->error->http ) && 404 == $result->error->http ) {
291
- /**
292
- * If the response code is 404, cache the result for half of the `$expiration`.
293
- *
294
- * @author Leo Fajardo (@leorw)
295
- * @since 2.2.4
296
- */
297
- $expiration /= 2;
298
- } else {
299
- // If no older data version and the response code is not 404, return result without
300
- // caching the error.
301
- return $result;
302
- }
303
- }
304
- }
305
-
306
- self::$_cache->set( $cache_key, $result, $expiration );
307
-
308
- $cached_result = $result;
309
- } else {
310
- $this->_logger->log( 'Using cached API result.' );
311
- }
312
-
313
- return $cached_result;
314
- }
315
-
316
- /**
317
- * Check if there's a cached version of the API request.
318
- *
319
- * @author Vova Feldman (@svovaf)
320
- * @since 1.2.1
321
- *
322
- * @param string $path
323
- * @param string $method
324
- * @param array $params
325
- *
326
- * @return bool
327
- */
328
- function is_cached( $path, $method = 'GET', $params = array() ) {
329
- $cache_key = $this->get_cache_key( $path, $method, $params );
330
-
331
- return self::$_cache->has_valid( $cache_key );
332
- }
333
-
334
- /**
335
- * Invalidate a cached version of the API request.
336
- *
337
- * @author Vova Feldman (@svovaf)
338
- * @since 1.2.1.5
339
- *
340
- * @param string $path
341
- * @param string $method
342
- * @param array $params
343
- */
344
- function purge_cache( $path, $method = 'GET', $params = array() ) {
345
- $this->_logger->entrance( "{$method}:{$path}" );
346
-
347
- $cache_key = $this->get_cache_key( $path, $method, $params );
348
-
349
- self::$_cache->purge( $cache_key );
350
- }
351
-
352
- /**
353
- * Invalidate a cached version of the API request.
354
- *
355
- * @author Vova Feldman (@svovaf)
356
- * @since 2.0.0
357
- *
358
- * @param string $path
359
- * @param int $expiration
360
- * @param string $method
361
- * @param array $params
362
- */
363
- function update_cache_expiration( $path, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $method = 'GET', $params = array() ) {
364
- $this->_logger->entrance( "{$method}:{$path}:{$expiration}" );
365
-
366
- $cache_key = $this->get_cache_key( $path, $method, $params );
367
-
368
- self::$_cache->update_expiration( $cache_key, $expiration );
369
- }
370
-
371
- /**
372
- * @param string $path
373
- * @param string $method
374
- * @param array $params
375
- *
376
- * @return string
377
- * @throws \Freemius_Exception
378
- */
379
- private function get_cache_key( $path, $method = 'GET', $params = array() ) {
380
- $canonized = $this->_api->CanonizePath( $path );
381
- // $exploded = explode('/', $canonized);
382
- // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params));
383
- return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' );
384
- }
385
-
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();
398
-
399
- $cache_key = 'ping_test';
400
-
401
- $test = self::$_cache->get_valid( $cache_key, null );
402
-
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
- /**
432
- * Check if API is temporary down.
433
- *
434
- * @author Vova Feldman (@svovaf)
435
- * @since 1.1.6
436
- *
437
- * @return bool
438
- */
439
- static function is_temporary_down() {
440
- self::_init();
441
-
442
- $test = self::$_cache->get_valid( 'ping_test', null );
443
-
444
- return ( false === $test );
445
- }
446
-
447
- /**
448
- * @author Vova Feldman (@svovaf)
449
- * @since 1.1.6
450
- *
451
- * @return object
452
- */
453
- private function get_temporary_unavailable_error() {
454
- return (object) array(
455
- 'error' => (object) array(
456
- 'type' => 'TemporaryUnavailable',
457
- 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.',
458
- 'code' => 'temporary_unavailable',
459
- 'http' => 503
460
- )
461
- );
462
- }
463
-
464
- /**
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
- * Check if based on the API result we should try
516
- * to re-run the same request with HTTP instead of HTTPS.
517
- *
518
- * @author Vova Feldman (@svovaf)
519
- * @since 1.1.6
520
- *
521
- * @param $result
522
- *
523
- * @return bool
524
- */
525
- private static function should_try_with_http( $result ) {
526
- if ( ! Freemius_Api_WordPress::IsHttps() ) {
527
- return false;
528
- }
529
-
530
- return ( ! is_object( $result ) ||
531
- ! isset( $result->error ) ||
532
- ! isset( $result->error->code ) ||
533
- ! in_array( $result->error->code, array(
534
- 'curl_missing',
535
- 'cloudflare_ddos_protection',
536
- 'maintenance_mode',
537
- 'squid_cache_block',
538
- 'too_many_requests',
539
- ) ) );
540
-
541
- }
542
-
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
- function get_url( $path = '' ) {
558
- return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() );
559
- }
560
-
561
- /**
562
- * Clear API cache.
563
- *
564
- * @author Vova Feldman (@svovaf)
565
- * @since 1.0.9
566
- */
567
- static function clear_cache() {
568
- self::_init();
569
-
570
- self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
571
- self::$_cache->clear();
572
- }
573
-
574
- #----------------------------------------------------------------------------------
575
- #region Error Handling
576
- #----------------------------------------------------------------------------------
577
-
578
- /**
579
- * @author Vova Feldman (@svovaf)
580
- * @since 1.2.1.5
581
- *
582
- * @param mixed $result
583
- *
584
- * @return bool Is API result contains an error.
585
- */
586
- static function is_api_error( $result ) {
587
- return ( is_object( $result ) && isset( $result->error ) ) ||
588
- is_string( $result );
589
- }
590
-
591
- /**
592
- * @author Vova Feldman (@svovaf)
593
- * @since 2.0.0
594
- *
595
- * @param mixed $result
596
- *
597
- * @return bool Is API result contains an error.
598
- */
599
- static function is_api_error_object( $result ) {
600
- return (
601
- is_object( $result ) &&
602
- isset( $result->error ) &&
603
- isset( $result->error->message )
604
- );
605
- }
606
-
607
- /**
608
- * Checks if given API result is a non-empty and not an error object.
609
- *
610
- * @author Vova Feldman (@svovaf)
611
- * @since 1.2.1.5
612
- *
613
- * @param mixed $result
614
- * @param string|null $required_property Optional property we want to verify that is set.
615
- *
616
- * @return bool
617
- */
618
- static function is_api_result_object( $result, $required_property = null ) {
619
- return (
620
- is_object( $result ) &&
621
- ! isset( $result->error ) &&
622
- ( empty( $required_property ) || isset( $result->{$required_property} ) )
623
- );
624
- }
625
-
626
- /**
627
- * Checks if given API result is a non-empty entity object with non-empty ID.
628
- *
629
- * @author Vova Feldman (@svovaf)
630
- * @since 1.2.1.5
631
- *
632
- * @param mixed $result
633
- *
634
- * @return bool
635
- */
636
- static function is_api_result_entity( $result ) {
637
- return self::is_api_result_object( $result, 'id' ) &&
638
- FS_Entity::is_valid_id( $result->id );
639
- }
640
-
641
- /**
642
- * Get API result error code. If failed to get code, returns an empty string.
643
- *
644
- * @author Vova Feldman (@svovaf)
645
- * @since 2.0.0
646
- *
647
- * @param mixed $result
648
- *
649
- * @return string
650
- */
651
- static function get_error_code( $result ) {
652
- if ( is_object( $result ) &&
653
- isset( $result->error ) &&
654
- is_object( $result->error ) &&
655
- ! empty( $result->error->code )
656
- ) {
657
- return $result->error->code;
658
- }
659
-
660
- return '';
661
- }
662
-
663
- #endregion
664
  }
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
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_Cache_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_WordPress
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
+ * @author Leo Fajardo (@leorw)
61
+ * @since 2.3.0
62
+ *
63
+ * @var string
64
+ */
65
+ private $_sdk_version;
66
+
67
+ /**
68
+ * @param string $slug
69
+ * @param string $scope 'app', 'developer', 'user' or 'install'.
70
+ * @param number $id Element's id.
71
+ * @param string $public_key Public key.
72
+ * @param bool $is_sandbox
73
+ * @param bool|string $secret_key Element's secret key.
74
+ * @param null|string $sdk_version
75
+ *
76
+ * @return FS_Api
77
+ */
78
+ static function instance(
79
+ $slug,
80
+ $scope,
81
+ $id,
82
+ $public_key,
83
+ $is_sandbox,
84
+ $secret_key = false,
85
+ $sdk_version = null
86
+ ) {
87
+ $identifier = md5( $slug . $scope . $id . $public_key . ( is_string( $secret_key ) ? $secret_key : '' ) . json_encode( $is_sandbox ) );
88
+
89
+ if ( ! isset( self::$_instances[ $identifier ] ) ) {
90
+ self::_init();
91
+
92
+ self::$_instances[ $identifier ] = new FS_Api( $slug, $scope, $id, $public_key, $secret_key, $is_sandbox, $sdk_version );
93
+ }
94
+
95
+ return self::$_instances[ $identifier ];
96
+ }
97
+
98
+ private static function _init() {
99
+ if ( isset( self::$_options ) ) {
100
+ return;
101
+ }
102
+
103
+ if ( ! class_exists( 'Freemius_Api_WordPress' ) ) {
104
+ require_once WP_FS__DIR_SDK . '/FreemiusWordPress.php';
105
+ }
106
+
107
+ self::$_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true, true );
108
+ self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
109
+
110
+ self::$_clock_diff = self::$_options->get_option( 'api_clock_diff', 0 );
111
+ Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff );
112
+
113
+ if ( self::$_options->get_option( 'api_force_http', false ) ) {
114
+ Freemius_Api_WordPress::SetHttp();
115
+ }
116
+ }
117
+
118
+ /**
119
+ * @param string $slug
120
+ * @param string $scope 'app', 'developer', 'user' or 'install'.
121
+ * @param number $id Element's id.
122
+ * @param string $public_key Public key.
123
+ * @param bool|string $secret_key Element's secret key.
124
+ * @param bool $is_sandbox
125
+ * @param null|string $sdk_version
126
+ */
127
+ private function __construct(
128
+ $slug,
129
+ $scope,
130
+ $id,
131
+ $public_key,
132
+ $secret_key,
133
+ $is_sandbox,
134
+ $sdk_version
135
+ ) {
136
+ $this->_api = new Freemius_Api_WordPress( $scope, $id, $public_key, $secret_key, $is_sandbox );
137
+
138
+ $this->_slug = $slug;
139
+ $this->_sdk_version = $sdk_version;
140
+ $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
141
+ }
142
+
143
+ /**
144
+ * Find clock diff between server and API server, and store the diff locally.
145
+ *
146
+ * @param bool|int $diff
147
+ *
148
+ * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds.
149
+ */
150
+ private function _sync_clock_diff( $diff = false ) {
151
+ $this->_logger->entrance();
152
+
153
+ // Sync clock and store.
154
+ $new_clock_diff = ( false === $diff ) ?
155
+ Freemius_Api_WordPress::FindClockDiff() :
156
+ $diff;
157
+
158
+ if ( $new_clock_diff === self::$_clock_diff ) {
159
+ return false;
160
+ }
161
+
162
+ self::$_clock_diff = $new_clock_diff;
163
+
164
+ // Update API clock's diff.
165
+ Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff );
166
+
167
+ // Store new clock diff in storage.
168
+ self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true );
169
+
170
+ return $new_clock_diff;
171
+ }
172
+
173
+ /**
174
+ * Override API call to enable retry with servers' clock auto sync method.
175
+ *
176
+ * @param string $path
177
+ * @param string $method
178
+ * @param array $params
179
+ * @param bool $retry Is in retry or first call attempt.
180
+ *
181
+ * @return array|mixed|string|void
182
+ */
183
+ private function _call( $path, $method = 'GET', $params = array(), $retry = false ) {
184
+ $this->_logger->entrance( $method . ':' . $path );
185
+
186
+ if ( self::is_temporary_down() ) {
187
+ $result = $this->get_temporary_unavailable_error();
188
+ } else {
189
+ /**
190
+ * @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.
191
+ */
192
+ if ( ! empty( $this->_sdk_version ) ) {
193
+ if ( false === strpos( $path, 'sdk_version=' ) &&
194
+ ! isset( $params['sdk_version'] )
195
+ ) {
196
+ // 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.
197
+ $path = add_query_arg( 'sdk_version', $this->_sdk_version, $path );
198
+ }
199
+ }
200
+
201
+ $result = $this->_api->Api( $path, $method, $params );
202
+
203
+ if ( null !== $result &&
204
+ isset( $result->error ) &&
205
+ isset( $result->error->code ) &&
206
+ 'request_expired' === $result->error->code
207
+ ) {
208
+ if ( ! $retry ) {
209
+ $diff = isset( $result->error->timestamp ) ?
210
+ ( time() - strtotime( $result->error->timestamp ) ) :
211
+ false;
212
+
213
+ // Try to sync clock diff.
214
+ if ( false !== $this->_sync_clock_diff( $diff ) ) {
215
+ // Retry call with new synced clock.
216
+ return $this->_call( $path, $method, $params, true );
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ if ( $this->_logger->is_on() && self::is_api_error( $result ) ) {
223
+ // Log API errors.
224
+ $this->_logger->api_error( $result );
225
+ }
226
+
227
+ return $result;
228
+ }
229
+
230
+ /**
231
+ * Override API call to wrap it in servers' clock sync method.
232
+ *
233
+ * @param string $path
234
+ * @param string $method
235
+ * @param array $params
236
+ *
237
+ * @return array|mixed|string|void
238
+ * @throws Freemius_Exception
239
+ */
240
+ function call( $path, $method = 'GET', $params = array() ) {
241
+ return $this->_call( $path, $method, $params );
242
+ }
243
+
244
+ /**
245
+ * Get API request URL signed via query string.
246
+ *
247
+ * @param string $path
248
+ *
249
+ * @return string
250
+ */
251
+ function get_signed_url( $path ) {
252
+ return $this->_api->GetSignedUrl( $path );
253
+ }
254
+
255
+ /**
256
+ * @param string $path
257
+ * @param bool $flush
258
+ * @param int $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours
259
+ *
260
+ * @return stdClass|mixed
261
+ */
262
+ function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
263
+ $this->_logger->entrance( $path );
264
+
265
+ $cache_key = $this->get_cache_key( $path );
266
+
267
+ // Always flush during development.
268
+ if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) {
269
+ $flush = true;
270
+ }
271
+
272
+ $cached_result = self::$_cache->get( $cache_key );
273
+
274
+ if ( $flush || ! self::$_cache->has_valid( $cache_key, $expiration ) ) {
275
+ $result = $this->call( $path );
276
+
277
+ if ( ! is_object( $result ) || isset( $result->error ) ) {
278
+ // Api returned an error.
279
+ if ( is_object( $cached_result ) &&
280
+ ! isset( $cached_result->error )
281
+ ) {
282
+ // If there was an error during a newer data fetch,
283
+ // fallback to older data version.
284
+ $result = $cached_result;
285
+
286
+ if ( $this->_logger->is_on() ) {
287
+ $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) );
288
+ }
289
+ } else {
290
+ if ( is_object( $result ) && isset( $result->error->http ) && 404 == $result->error->http ) {
291
+ /**
292
+ * If the response code is 404, cache the result for half of the `$expiration`.
293
+ *
294
+ * @author Leo Fajardo (@leorw)
295
+ * @since 2.2.4
296
+ */
297
+ $expiration /= 2;
298
+ } else {
299
+ // If no older data version and the response code is not 404, return result without
300
+ // caching the error.
301
+ return $result;
302
+ }
303
+ }
304
+ }
305
+
306
+ self::$_cache->set( $cache_key, $result, $expiration );
307
+
308
+ $cached_result = $result;
309
+ } else {
310
+ $this->_logger->log( 'Using cached API result.' );
311
+ }
312
+
313
+ return $cached_result;
314
+ }
315
+
316
+ /**
317
+ * Check if there's a cached version of the API request.
318
+ *
319
+ * @author Vova Feldman (@svovaf)
320
+ * @since 1.2.1
321
+ *
322
+ * @param string $path
323
+ * @param string $method
324
+ * @param array $params
325
+ *
326
+ * @return bool
327
+ */
328
+ function is_cached( $path, $method = 'GET', $params = array() ) {
329
+ $cache_key = $this->get_cache_key( $path, $method, $params );
330
+
331
+ return self::$_cache->has_valid( $cache_key );
332
+ }
333
+
334
+ /**
335
+ * Invalidate a cached version of the API request.
336
+ *
337
+ * @author Vova Feldman (@svovaf)
338
+ * @since 1.2.1.5
339
+ *
340
+ * @param string $path
341
+ * @param string $method
342
+ * @param array $params
343
+ */
344
+ function purge_cache( $path, $method = 'GET', $params = array() ) {
345
+ $this->_logger->entrance( "{$method}:{$path}" );
346
+
347
+ $cache_key = $this->get_cache_key( $path, $method, $params );
348
+
349
+ self::$_cache->purge( $cache_key );
350
+ }
351
+
352
+ /**
353
+ * Invalidate a cached version of the API request.
354
+ *
355
+ * @author Vova Feldman (@svovaf)
356
+ * @since 2.0.0
357
+ *
358
+ * @param string $path
359
+ * @param int $expiration
360
+ * @param string $method
361
+ * @param array $params
362
+ */
363
+ function update_cache_expiration( $path, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $method = 'GET', $params = array() ) {
364
+ $this->_logger->entrance( "{$method}:{$path}:{$expiration}" );
365
+
366
+ $cache_key = $this->get_cache_key( $path, $method, $params );
367
+
368
+ self::$_cache->update_expiration( $cache_key, $expiration );
369
+ }
370
+
371
+ /**
372
+ * @param string $path
373
+ * @param string $method
374
+ * @param array $params
375
+ *
376
+ * @return string
377
+ * @throws \Freemius_Exception
378
+ */
379
+ private function get_cache_key( $path, $method = 'GET', $params = array() ) {
380
+ $canonized = $this->_api->CanonizePath( $path );
381
+ // $exploded = explode('/', $canonized);
382
+ // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params));
383
+ return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' );
384
+ }
385
+
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();
398
+
399
+ $cache_key = 'ping_test';
400
+
401
+ $test = self::$_cache->get_valid( $cache_key, null );
402
+
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
+ /**
432
+ * Check if API is temporary down.
433
+ *
434
+ * @author Vova Feldman (@svovaf)
435
+ * @since 1.1.6
436
+ *
437
+ * @return bool
438
+ */
439
+ static function is_temporary_down() {
440
+ self::_init();
441
+
442
+ $test = self::$_cache->get_valid( 'ping_test', null );
443
+
444
+ return ( false === $test );
445
+ }
446
+
447
+ /**
448
+ * @author Vova Feldman (@svovaf)
449
+ * @since 1.1.6
450
+ *
451
+ * @return object
452
+ */
453
+ private function get_temporary_unavailable_error() {
454
+ return (object) array(
455
+ 'error' => (object) array(
456
+ 'type' => 'TemporaryUnavailable',
457
+ 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.',
458
+ 'code' => 'temporary_unavailable',
459
+ 'http' => 503
460
+ )
461
+ );
462
+ }
463
+
464
+ /**
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
+ * Check if based on the API result we should try
516
+ * to re-run the same request with HTTP instead of HTTPS.
517
+ *
518
+ * @author Vova Feldman (@svovaf)
519
+ * @since 1.1.6
520
+ *
521
+ * @param $result
522
+ *
523
+ * @return bool
524
+ */
525
+ private static function should_try_with_http( $result ) {
526
+ if ( ! Freemius_Api_WordPress::IsHttps() ) {
527
+ return false;
528
+ }
529
+
530
+ return ( ! is_object( $result ) ||
531
+ ! isset( $result->error ) ||
532
+ ! isset( $result->error->code ) ||
533
+ ! in_array( $result->error->code, array(
534
+ 'curl_missing',
535
+ 'cloudflare_ddos_protection',
536
+ 'maintenance_mode',
537
+ 'squid_cache_block',
538
+ 'too_many_requests',
539
+ ) ) );
540
+
541
+ }
542
+
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
+ function get_url( $path = '' ) {
558
+ return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() );
559
+ }
560
+
561
+ /**
562
+ * Clear API cache.
563
+ *
564
+ * @author Vova Feldman (@svovaf)
565
+ * @since 1.0.9
566
+ */
567
+ static function clear_cache() {
568
+ self::_init();
569
+
570
+ self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME );
571
+ self::$_cache->clear();
572
+ }
573
+
574
+ #----------------------------------------------------------------------------------
575
+ #region Error Handling
576
+ #----------------------------------------------------------------------------------
577
+
578
+ /**
579
+ * @author Vova Feldman (@svovaf)
580
+ * @since 1.2.1.5
581
+ *
582
+ * @param mixed $result
583
+ *
584
+ * @return bool Is API result contains an error.
585
+ */
586
+ static function is_api_error( $result ) {
587
+ return ( is_object( $result ) && isset( $result->error ) ) ||
588
+ is_string( $result );
589
+ }
590
+
591
+ /**
592
+ * @author Vova Feldman (@svovaf)
593
+ * @since 2.0.0
594
+ *
595
+ * @param mixed $result
596
+ *
597
+ * @return bool Is API result contains an error.
598
+ */
599
+ static function is_api_error_object( $result ) {
600
+ return (
601
+ is_object( $result ) &&
602
+ isset( $result->error ) &&
603
+ isset( $result->error->message )
604
+ );
605
+ }
606
+
607
+ /**
608
+ * Checks if given API result is a non-empty and not an error object.
609
+ *
610
+ * @author Vova Feldman (@svovaf)
611
+ * @since 1.2.1.5
612
+ *
613
+ * @param mixed $result
614
+ * @param string|null $required_property Optional property we want to verify that is set.
615
+ *
616
+ * @return bool
617
+ */
618
+ static function is_api_result_object( $result, $required_property = null ) {
619
+ return (
620
+ is_object( $result ) &&
621
+ ! isset( $result->error ) &&
622
+ ( empty( $required_property ) || isset( $result->{$required_property} ) )
623
+ );
624
+ }
625
+
626
+ /**
627
+ * Checks if given API result is a non-empty entity object with non-empty ID.
628
+ *
629
+ * @author Vova Feldman (@svovaf)
630
+ * @since 1.2.1.5
631
+ *
632
+ * @param mixed $result
633
+ *
634
+ * @return bool
635
+ */
636
+ static function is_api_result_entity( $result ) {
637
+ return self::is_api_result_object( $result, 'id' ) &&
638
+ FS_Entity::is_valid_id( $result->id );
639
+ }
640
+
641
+ /**
642
+ * Get API result error code. If failed to get code, returns an empty string.
643
+ *
644
+ * @author Vova Feldman (@svovaf)
645
+ * @since 2.0.0
646
+ *
647
+ * @param mixed $result
648
+ *
649
+ * @return string
650
+ */
651
+ static function get_error_code( $result ) {
652
+ if ( is_object( $result ) &&
653
+ isset( $result->error ) &&
654
+ is_object( $result->error ) &&
655
+ ! empty( $result->error->code )
656
+ ) {
657
+ return $result->error->code;
658
+ }
659
+
660
+ return '';
661
+ }
662
+
663
+ #endregion
664
  }
freemius/includes/class-fs-options.php CHANGED
@@ -1,431 +1,431 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.2.3
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * Class FS_Options
15
- *
16
- * A wrapper class for handling network level and single site level options.
17
- */
18
- class FS_Options {
19
- /**
20
- * @var string
21
- */
22
- private $_id;
23
-
24
- /**
25
- * @var array[string]FS_Options {
26
- * @key string
27
- * @value FS_Options
28
- * }
29
- */
30
- private static $_instances;
31
-
32
- /**
33
- * @var FS_Option_Manager Site level options.
34
- */
35
- private $_options;
36
-
37
- /**
38
- * @var FS_Option_Manager Network level options.
39
- */
40
- private $_network_options;
41
-
42
- /**
43
- * @var int The ID of the blog that is associated with the current site level options.
44
- */
45
- private $_blog_id = 0;
46
-
47
- /**
48
- * @var bool
49
- */
50
- private $_is_multisite;
51
-
52
- /**
53
- * @var string[] Lazy collection of params on the site level.
54
- */
55
- private static $_SITE_OPTIONS_MAP;
56
-
57
- /**
58
- * @author Leo Fajardo (@leorw)
59
- * @since 2.0.0
60
- *
61
- * @param string $id
62
- * @param bool $load
63
- *
64
- * @return FS_Options
65
- */
66
- static function instance( $id, $load = false ) {
67
- if ( ! isset( self::$_instances[ $id ] ) ) {
68
- self::$_instances[ $id ] = new FS_Options( $id, $load );
69
- }
70
-
71
- return self::$_instances[ $id ];
72
- }
73
-
74
- /**
75
- * @author Leo Fajardo (@leorw)
76
- * @since 2.0.0
77
- *
78
- * @param string $id
79
- * @param bool $load
80
- */
81
- private function __construct( $id, $load = false ) {
82
- $this->_id = $id;
83
- $this->_is_multisite = is_multisite();
84
-
85
- if ( $this->_is_multisite ) {
86
- $this->_blog_id = get_current_blog_id();
87
- $this->_network_options = FS_Option_Manager::get_manager( $id, $load, true );
88
- }
89
-
90
- $this->_options = FS_Option_Manager::get_manager( $id, $load, $this->_blog_id );
91
- }
92
-
93
- /**
94
- * Switch the context of the site level options manager.
95
- *
96
- * @author Vova Feldman (@svovaf)
97
- * @since 2.0.0
98
- *
99
- * @param $blog_id
100
- */
101
- function set_site_blog_context( $blog_id ) {
102
- $this->_blog_id = $blog_id;
103
-
104
- $this->_options = FS_Option_Manager::get_manager( $this->_id, false, $this->_blog_id );
105
- }
106
-
107
- /**
108
- * @author Leo Fajardo (@leorw)
109
- *
110
- * @param string $option
111
- * @param mixed $default
112
- * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS).
113
- *
114
- * @return mixed
115
- */
116
- function get_option( $option, $default = null, $network_level_or_blog_id = null ) {
117
- if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) {
118
- return $this->_network_options->get_option( $option, $default );
119
- }
120
-
121
- $site_options = $this->get_site_options( $network_level_or_blog_id );
122
-
123
- return $site_options->get_option( $option, $default );
124
- }
125
-
126
- /**
127
- * @author Leo Fajardo (@leorw)
128
- * @since 2.0.0
129
- *
130
- * @param string $option
131
- * @param mixed $value
132
- * @param bool $flush
133
- * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS).
134
- */
135
- function set_option( $option, $value, $flush = false, $network_level_or_blog_id = null ) {
136
- if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) {
137
- $this->_network_options->set_option( $option, $value, $flush );
138
- } else {
139
- $site_options = $this->get_site_options( $network_level_or_blog_id );
140
- $site_options->set_option( $option, $value, $flush );
141
- }
142
- }
143
-
144
- /**
145
- * @author Vova Feldman (@svovaf)
146
- * @since 2.0.0
147
- *
148
- * @param string $option
149
- * @param bool $flush
150
- * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS).
151
- */
152
- function unset_option( $option, $flush = false, $network_level_or_blog_id = null ) {
153
- if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) {
154
- $this->_network_options->unset_option( $option, $flush );
155
- } else {
156
- $site_options = $this->get_site_options( $network_level_or_blog_id );
157
- $site_options->unset_option( $option, $flush );
158
- }
159
- }
160
-
161
- /**
162
- * @author Leo Fajardo (@leorw)
163
- * @since 2.0.0
164
- *
165
- * @param bool $flush
166
- * @param bool $network_level
167
- */
168
- function load( $flush = false, $network_level = true ) {
169
- if ( $this->_is_multisite && $network_level ) {
170
- $this->_network_options->load( $flush );
171
- } else {
172
- $this->_options->load( $flush );
173
- }
174
- }
175
-
176
- /**
177
- * @author Leo Fajardo (@leorw)
178
- * @since 2.0.0
179
- *
180
- * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, store both network storage and the current context blog storage.
181
- */
182
- function store( $network_level_or_blog_id = null ) {
183
- if ( ! $this->_is_multisite ||
184
- false === $network_level_or_blog_id ||
185
- 0 == $network_level_or_blog_id ||
186
- is_null( $network_level_or_blog_id )
187
- ) {
188
- $site_options = $this->get_site_options( $network_level_or_blog_id );
189
- $site_options->store();
190
- }
191
-
192
- if ( $this->_is_multisite &&
193
- ( is_null( $network_level_or_blog_id ) || true === $network_level_or_blog_id )
194
- ) {
195
- $this->_network_options->store();
196
- }
197
- }
198
-
199
- /**
200
- * @author Vova Feldman (@svovaf)
201
- * @since 2.0.0
202
- *
203
- * @param int|null|bool $network_level_or_blog_id
204
- * @param bool $flush
205
- */
206
- function clear( $network_level_or_blog_id = null, $flush = false ) {
207
- if ( ! $this->_is_multisite ||
208
- false === $network_level_or_blog_id ||
209
- is_null( $network_level_or_blog_id ) ||
210
- is_numeric( $network_level_or_blog_id )
211
- ) {
212
- $site_options = $this->get_site_options( $network_level_or_blog_id );
213
- $site_options->clear( $flush );
214
- }
215
-
216
- if ( $this->_is_multisite &&
217
- ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) )
218
- ) {
219
- $this->_network_options->clear( $flush );
220
- }
221
- }
222
-
223
- /**
224
- * Migration script to the new storage data structure that is network compatible.
225
- *
226
- * IMPORTANT:
227
- * This method should be executed only after it is determined if this is a network
228
- * level compatible product activation.
229
- *
230
- * @author Vova Feldman (@svovaf)
231
- * @since 2.0.0
232
- *
233
- * @param int $blog_id
234
- */
235
- function migrate_to_network( $blog_id = 0 ) {
236
- if ( ! $this->_is_multisite ) {
237
- return;
238
- }
239
-
240
- $updated = false;
241
-
242
- $site_options = $this->get_site_options( $blog_id );
243
-
244
- $keys = $site_options->get_options_keys();
245
-
246
- foreach ( $keys as $option ) {
247
- if ( $this->is_site_option( $option ) ||
248
- // Don't move admin notices to the network storage.
249
- in_array($option, array(
250
- // Don't move admin notices to the network storage.
251
- 'admin_notices',
252
- // Don't migrate the module specific data, it will be migrated by the FS_Storage.
253
- 'plugin_data',
254
- 'theme_data',
255
- ))
256
- ) {
257
- continue;
258
- }
259
-
260
- $option_updated = false;
261
-
262
- // Migrate option to the network storage.
263
- $site_option = $site_options->get_option( $option );
264
-
265
- if ( ! $this->_network_options->has_option( $option ) ) {
266
- // Option not set on the network level, so just set it.
267
- $this->_network_options->set_option( $option, $site_option, false );
268
-
269
- $option_updated = true;
270
- } else {
271
- // Option already set on the network level, so we need to merge it inelegantly.
272
- $network_option = $this->_network_options->get_option( $option );
273
-
274
- if ( is_array( $network_option ) && is_array( $site_option ) ) {
275
- // Option is an array.
276
- foreach ( $site_option as $key => $value ) {
277
- if ( ! isset( $network_option[ $key ] ) ) {
278
- $network_option[ $key ] = $value;
279
-
280
- $option_updated = true;
281
- } else if ( is_array( $network_option[ $key ] ) && is_array( $value ) ) {
282
- if ( empty( $network_option[ $key ] ) ) {
283
- $network_option[ $key ] = $value;
284
-
285
- $option_updated = true;
286
- } else if ( empty( $value ) ) {
287
- // Do nothing.
288
- } else {
289
- reset($value);
290
- $first_key = key($value);
291
- if ( $value[$first_key] instanceof FS_Entity ) {
292
- // Merge entities by IDs.
293
- $network_entities_ids = array();
294
- foreach ( $network_option[ $key ] as $entity ) {
295
- $network_entities_ids[ $entity->id ] = true;
296
- }
297
-
298
- foreach ( $value as $entity ) {
299
- if ( ! isset( $network_entities_ids[ $entity->id ] ) ) {
300
- $network_option[ $key ][] = $entity;
301
-
302
- $option_updated = true;
303
- }
304
- }
305
- }
306
- }
307
- }
308
- }
309
- }
310
-
311
- if ( $option_updated ) {
312
- $this->_network_options->set_option( $option, $network_option, false );
313
- }
314
- }
315
-
316
- /**
317
- * Remove the option from site level storage.
318
- *
319
- * IMPORTANT:
320
- * The line below is intentionally commented since we want to preserve the option
321
- * on the site storage level for "downgrade compatibility". Basically, if the user
322
- * will downgrade to an older version of the plugin with the prev storage structure,
323
- * it will continue working.
324
- *
325
- * @todo After a few releases we can remove this.
326
- */
327
- // $site_options->unset_option($option, false);
328
-
329
- if ( $option_updated ) {
330
- $updated = true;
331
- }
332
- }
333
-
334
- if ( ! $updated ) {
335
- return;
336
- }
337
-
338
- // Update network level storage.
339
- $this->_network_options->store();
340
- // $site_options->store();
341
- }
342
-
343
-
344
- #--------------------------------------------------------------------------------
345
- #region Helper Methods
346
- #--------------------------------------------------------------------------------
347
-
348
- /**
349
- * We don't want to load the map right away since it's not even needed in a non-MS environment.
350
- *
351
- * @author Vova Feldman (@svovaf)
352
- * @since 2.0.0
353
- */
354
- private static function load_site_options_map() {
355
- self::$_SITE_OPTIONS_MAP = array(
356
- 'sites' => true,
357
- 'theme_sites' => true,
358
- 'unique_id' => true,
359
- 'active_plugins' => true,
360
- );
361
- }
362
-
363
- /**
364
- * @author Vova Feldman (@svovaf)
365
- * @since 2.0.0
366
- *
367
- * @param string $option
368
- *
369
- * @return bool
370
- */
371
- private function is_site_option( $option ) {
372
- if ( WP_FS__ACCOUNTS_OPTION_NAME != $this->_id ) {
373
- return false;
374
- }
375
-
376
- if ( ! isset( self::$_SITE_OPTIONS_MAP ) ) {
377
- self::load_site_options_map();
378
- }
379
-
380
- return isset( self::$_SITE_OPTIONS_MAP[ $option ] );
381
- }
382
-
383
- /**
384
- * @author Vova Feldman (@svovaf)
385
- * @since 2.0.0
386
- *
387
- * @param int $blog_id
388
- *
389
- * @return FS_Option_Manager
390
- */
391
- private function get_site_options( $blog_id = 0 ) {
392
- if ( 0 == $blog_id || $blog_id == $this->_blog_id ) {
393
- return $this->_options;
394
- }
395
-
396
- return FS_Option_Manager::get_manager( $this->_id, true, $blog_id );
397
- }
398
-
399
- /**
400
- * Check if an option should be stored on the MS network storage.
401
- *
402
- * @author Vova Feldman (@svovaf)
403
- * @since 2.0.0
404
- *
405
- * @param string $option
406
- * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS).
407
- *
408
- * @return bool
409
- */
410
- private function should_use_network_storage( $option, $network_level_or_blog_id = null ) {
411
- if ( ! $this->_is_multisite ) {
412
- // Not a multisite environment.
413
- return false;
414
- }
415
-
416
- if ( is_numeric( $network_level_or_blog_id ) ) {
417
- // Explicitly asked to use a specified blog storage.
418
- return false;
419
- }
420
-
421
- if ( is_bool( $network_level_or_blog_id ) ) {
422
- // Explicitly specified whether should use the network or blog level storage.
423
- return $network_level_or_blog_id;
424
- }
425
-
426
- // Determine which storage to use based on the option.
427
- return ! $this->is_site_option( $option );
428
- }
429
-
430
- #endregion
431
  }
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.2.3
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * Class FS_Options
15
+ *
16
+ * A wrapper class for handling network level and single site level options.
17
+ */
18
+ class FS_Options {
19
+ /**
20
+ * @var string
21
+ */
22
+ private $_id;
23
+
24
+ /**
25
+ * @var array[string]FS_Options {
26
+ * @key string
27
+ * @value FS_Options
28
+ * }
29
+ */
30
+ private static $_instances;
31
+
32
+ /**
33
+ * @var FS_Option_Manager Site level options.
34
+ */
35
+ private $_options;
36
+
37
+ /**
38
+ * @var FS_Option_Manager Network level options.
39
+ */
40
+ private $_network_options;
41
+
42
+ /**
43
+ * @var int The ID of the blog that is associated with the current site level options.
44
+ */
45
+ private $_blog_id = 0;
46
+
47
+ /**
48
+ * @var bool
49
+ */
50
+ private $_is_multisite;
51
+
52
+ /**
53
+ * @var string[] Lazy collection of params on the site level.
54
+ */
55
+ private static $_SITE_OPTIONS_MAP;
56
+
57
+ /**
58
+ * @author Leo Fajardo (@leorw)
59
+ * @since 2.0.0
60
+ *
61
+ * @param string $id
62
+ * @param bool $load
63
+ *
64
+ * @return FS_Options
65
+ */
66
+ static function instance( $id, $load = false ) {
67
+ if ( ! isset( self::$_instances[ $id ] ) ) {
68
+ self::$_instances[ $id ] = new FS_Options( $id, $load );
69
+ }
70
+
71
+ return self::$_instances[ $id ];
72
+ }
73
+
74
+ /**
75
+ * @author Leo Fajardo (@leorw)
76
+ * @since 2.0.0
77
+ *
78
+ * @param string $id
79
+ * @param bool $load
80
+ */
81
+ private function __construct( $id, $load = false ) {
82
+ $this->_id = $id;
83
+ $this->_is_multisite = is_multisite();
84
+
85
+ if ( $this->_is_multisite ) {
86
+ $this->_blog_id = get_current_blog_id();
87
+ $this->_network_options = FS_Option_Manager::get_manager( $id, $load, true );
88
+ }
89
+
90
+ $this->_options = FS_Option_Manager::get_manager( $id, $load, $this->_blog_id );
91
+ }
92
+
93
+ /**
94
+ * Switch the context of the site level options manager.
95
+ *
96
+ * @author Vova Feldman (@svovaf)
97
+ * @since 2.0.0
98
+ *
99
+ * @param $blog_id
100
+ */
101
+ function set_site_blog_context( $blog_id ) {
102
+ $this->_blog_id = $blog_id;
103
+
104
+ $this->_options = FS_Option_Manager::get_manager( $this->_id, false, $this->_blog_id );
105
+ }
106
+
107
+ /**
108
+ * @author Leo Fajardo (@leorw)
109
+ *
110
+ * @param string $option
111
+ * @param mixed $default
112
+ * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS).
113
+ *
114
+ * @return mixed
115
+ */
116
+ function get_option( $option, $default = null, $network_level_or_blog_id = null ) {
117
+ if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) {
118
+ return $this->_network_options->get_option( $option, $default );
119
+ }
120
+
121
+ $site_options = $this->get_site_options( $network_level_or_blog_id );
122
+
123
+ return $site_options->get_option( $option, $default );
124
+ }
125
+
126
+ /**
127
+ * @author Leo Fajardo (@leorw)
128
+ * @since 2.0.0
129
+ *
130
+ * @param string $option
131
+ * @param mixed $value
132
+ * @param bool $flush
133
+ * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS).
134
+ */
135
+ function set_option( $option, $value, $flush = false, $network_level_or_blog_id = null ) {
136
+ if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) {
137
+ $this->_network_options->set_option( $option, $value, $flush );
138
+ } else {
139
+ $site_options = $this->get_site_options( $network_level_or_blog_id );
140
+ $site_options->set_option( $option, $value, $flush );
141
+ }
142
+ }
143
+
144
+ /**
145
+ * @author Vova Feldman (@svovaf)
146
+ * @since 2.0.0
147
+ *
148
+ * @param string $option
149
+ * @param bool $flush
150
+ * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS).
151
+ */
152
+ function unset_option( $option, $flush = false, $network_level_or_blog_id = null ) {
153
+ if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) {
154
+ $this->_network_options->unset_option( $option, $flush );
155
+ } else {
156
+ $site_options = $this->get_site_options( $network_level_or_blog_id );
157
+ $site_options->unset_option( $option, $flush );
158
+ }
159
+ }
160
+
161
+ /**
162
+ * @author Leo Fajardo (@leorw)
163
+ * @since 2.0.0
164
+ *
165
+ * @param bool $flush
166
+ * @param bool $network_level
167
+ */
168
+ function load( $flush = false, $network_level = true ) {
169
+ if ( $this->_is_multisite && $network_level ) {
170
+ $this->_network_options->load( $flush );
171
+ } else {
172
+ $this->_options->load( $flush );
173
+ }
174
+ }
175
+
176
+ /**
177
+ * @author Leo Fajardo (@leorw)
178
+ * @since 2.0.0
179
+ *
180
+ * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, store both network storage and the current context blog storage.
181
+ */
182
+ function store( $network_level_or_blog_id = null ) {
183
+ if ( ! $this->_is_multisite ||
184
+ false === $network_level_or_blog_id ||
185
+ 0 == $network_level_or_blog_id ||
186
+ is_null( $network_level_or_blog_id )
187
+ ) {
188
+ $site_options = $this->get_site_options( $network_level_or_blog_id );
189
+ $site_options->store();
190
+ }
191
+
192
+ if ( $this->_is_multisite &&
193
+ ( is_null( $network_level_or_blog_id ) || true === $network_level_or_blog_id )
194
+ ) {
195
+ $this->_network_options->store();
196
+ }
197
+ }
198
+
199
+ /**
200
+ * @author Vova Feldman (@svovaf)
201
+ * @since 2.0.0
202
+ *
203
+ * @param int|null|bool $network_level_or_blog_id
204
+ * @param bool $flush
205
+ */
206
+ function clear( $network_level_or_blog_id = null, $flush = false ) {
207
+ if ( ! $this->_is_multisite ||
208
+ false === $network_level_or_blog_id ||
209
+ is_null( $network_level_or_blog_id ) ||
210
+ is_numeric( $network_level_or_blog_id )
211
+ ) {
212
+ $site_options = $this->get_site_options( $network_level_or_blog_id );
213
+ $site_options->clear( $flush );
214
+ }
215
+
216
+ if ( $this->_is_multisite &&
217
+ ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) )
218
+ ) {
219
+ $this->_network_options->clear( $flush );
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Migration script to the new storage data structure that is network compatible.
225
+ *
226
+ * IMPORTANT:
227
+ * This method should be executed only after it is determined if this is a network
228
+ * level compatible product activation.
229
+ *
230
+ * @author Vova Feldman (@svovaf)
231
+ * @since 2.0.0
232
+ *
233
+ * @param int $blog_id
234
+ */
235
+ function migrate_to_network( $blog_id = 0 ) {
236
+ if ( ! $this->_is_multisite ) {
237
+ return;
238
+ }
239
+
240
+ $updated = false;
241
+
242
+ $site_options = $this->get_site_options( $blog_id );
243
+
244
+ $keys = $site_options->get_options_keys();
245
+
246
+ foreach ( $keys as $option ) {
247
+ if ( $this->is_site_option( $option ) ||
248
+ // Don't move admin notices to the network storage.
249
+ in_array($option, array(
250
+ // Don't move admin notices to the network storage.
251
+ 'admin_notices',
252
+ // Don't migrate the module specific data, it will be migrated by the FS_Storage.
253
+ 'plugin_data',
254
+ 'theme_data',
255
+ ))
256
+ ) {
257
+ continue;
258
+ }
259
+
260
+ $option_updated = false;
261
+
262
+ // Migrate option to the network storage.
263
+ $site_option = $site_options->get_option( $option );
264
+
265
+ if ( ! $this->_network_options->has_option( $option ) ) {
266
+ // Option not set on the network level, so just set it.
267
+ $this->_network_options->set_option( $option, $site_option, false );
268
+
269
+ $option_updated = true;
270
+ } else {
271
+ // Option already set on the network level, so we need to merge it inelegantly.
272
+ $network_option = $this->_network_options->get_option( $option );
273
+
274
+ if ( is_array( $network_option ) && is_array( $site_option ) ) {
275
+ // Option is an array.
276
+ foreach ( $site_option as $key => $value ) {
277
+ if ( ! isset( $network_option[ $key ] ) ) {
278
+ $network_option[ $key ] = $value;
279
+
280
+ $option_updated = true;
281
+ } else if ( is_array( $network_option[ $key ] ) && is_array( $value ) ) {
282
+ if ( empty( $network_option[ $key ] ) ) {
283
+ $network_option[ $key ] = $value;
284
+
285
+ $option_updated = true;
286
+ } else if ( empty( $value ) ) {
287
+ // Do nothing.
288
+ } else {
289
+ reset($value);
290
+ $first_key = key($value);
291
+ if ( $value[$first_key] instanceof FS_Entity ) {
292
+ // Merge entities by IDs.
293
+ $network_entities_ids = array();
294
+ foreach ( $network_option[ $key ] as $entity ) {
295
+ $network_entities_ids[ $entity->id ] = true;
296
+ }
297
+
298
+ foreach ( $value as $entity ) {
299
+ if ( ! isset( $network_entities_ids[ $entity->id ] ) ) {
300
+ $network_option[ $key ][] = $entity;
301
+
302
+ $option_updated = true;
303
+ }
304
+ }
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+
311
+ if ( $option_updated ) {
312
+ $this->_network_options->set_option( $option, $network_option, false );
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Remove the option from site level storage.
318
+ *
319
+ * IMPORTANT:
320
+ * The line below is intentionally commented since we want to preserve the option
321
+ * on the site storage level for "downgrade compatibility". Basically, if the user
322
+ * will downgrade to an older version of the plugin with the prev storage structure,
323
+ * it will continue working.
324
+ *
325
+ * @todo After a few releases we can remove this.
326
+ */
327
+ // $site_options->unset_option($option, false);
328
+
329
+ if ( $option_updated ) {
330
+ $updated = true;
331
+ }
332
+ }
333
+
334
+ if ( ! $updated ) {
335
+ return;
336
+ }
337
+
338
+ // Update network level storage.
339
+ $this->_network_options->store();
340
+ // $site_options->store();
341
+ }
342
+
343
+
344
+ #--------------------------------------------------------------------------------
345
+ #region Helper Methods
346
+ #--------------------------------------------------------------------------------
347
+
348
+ /**
349
+ * We don't want to load the map right away since it's not even needed in a non-MS environment.
350
+ *
351
+ * @author Vova Feldman (@svovaf)
352
+ * @since 2.0.0
353
+ */
354
+ private static function load_site_options_map() {
355
+ self::$_SITE_OPTIONS_MAP = array(
356
+ 'sites' => true,
357
+ 'theme_sites' => true,
358
+ 'unique_id' => true,
359
+ 'active_plugins' => true,
360
+ );
361
+ }
362
+
363
+ /**
364
+ * @author Vova Feldman (@svovaf)
365
+ * @since 2.0.0
366
+ *
367
+ * @param string $option
368
+ *
369
+ * @return bool
370
+ */
371
+ private function is_site_option( $option ) {
372
+ if ( WP_FS__ACCOUNTS_OPTION_NAME != $this->_id ) {
373
+ return false;
374
+ }
375
+
376
+ if ( ! isset( self::$_SITE_OPTIONS_MAP ) ) {
377
+ self::load_site_options_map();
378
+ }
379
+
380
+ return isset( self::$_SITE_OPTIONS_MAP[ $option ] );
381
+ }
382
+
383
+ /**
384
+ * @author Vova Feldman (@svovaf)
385
+ * @since 2.0.0
386
+ *
387
+ * @param int $blog_id
388
+ *
389
+ * @return FS_Option_Manager
390
+ */
391
+ private function get_site_options( $blog_id = 0 ) {
392
+ if ( 0 == $blog_id || $blog_id == $this->_blog_id ) {
393
+ return $this->_options;
394
+ }
395
+
396
+ return FS_Option_Manager::get_manager( $this->_id, true, $blog_id );
397
+ }
398
+
399
+ /**
400
+ * Check if an option should be stored on the MS network storage.
401
+ *
402
+ * @author Vova Feldman (@svovaf)
403
+ * @since 2.0.0
404
+ *
405
+ * @param string $option
406
+ * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_SITE_LEVEL_PARAMS).
407
+ *
408
+ * @return bool
409
+ */
410
+ private function should_use_network_storage( $option, $network_level_or_blog_id = null ) {
411
+ if ( ! $this->_is_multisite ) {
412
+ // Not a multisite environment.
413
+ return false;
414
+ }
415
+
416
+ if ( is_numeric( $network_level_or_blog_id ) ) {
417
+ // Explicitly asked to use a specified blog storage.
418
+ return false;
419
+ }
420
+
421
+ if ( is_bool( $network_level_or_blog_id ) ) {
422
+ // Explicitly specified whether should use the network or blog level storage.
423
+ return $network_level_or_blog_id;
424
+ }
425
+
426
+ // Determine which storage to use based on the option.
427
+ return ! $this->is_site_option( $option );
428
+ }
429
+
430
+ #endregion
431
  }
freemius/includes/class-fs-plugin-updater.php CHANGED
@@ -1,1561 +1,1561 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.0.4
7
- *
8
- * @link https://github.com/easydigitaldownloads/EDD-License-handler/blob/master/EDD_SL_Plugin_Updater.php
9
- */
10
-
11
- if ( ! defined( 'ABSPATH' ) ) {
12
- exit;
13
- }
14
-
15
- class FS_Plugin_Updater {
16
-
17
- /**
18
- * @var Freemius
19
- * @since 1.0.4
20
- */
21
- private $_fs;
22
- /**
23
- * @var FS_Logger
24
- * @since 1.0.4
25
- */
26
- private $_logger;
27
- /**
28
- * @var object
29
- * @since 1.1.8.1
30
- */
31
- private $_update_details;
32
- /**
33
- * @var array
34
- * @since 2.1.2
35
- */
36
- private $_translation_updates;
37
-
38
- private static $_upgrade_basename = null;
39
-
40
- #--------------------------------------------------------------------------------
41
- #region Singleton
42
- #--------------------------------------------------------------------------------
43
-
44
- /**
45
- * @var FS_Plugin_Updater[]
46
- * @since 2.0.0
47
- */
48
- private static $_INSTANCES = array();
49
-
50
- /**
51
- * @param Freemius $freemius
52
- *
53
- * @return FS_Plugin_Updater
54
- */
55
- static function instance( Freemius $freemius ) {
56
- $key = $freemius->get_id();
57
-
58
- if ( ! isset( self::$_INSTANCES[ $key ] ) ) {
59
- self::$_INSTANCES[ $key ] = new self( $freemius );
60
- }
61
-
62
- return self::$_INSTANCES[ $key ];
63
- }
64
-
65
- #endregion
66
-
67
- private function __construct( Freemius $freemius ) {
68
- $this->_fs = $freemius;
69
-
70
- $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
71
-
72
- $this->filters();
73
- }
74
-
75
- /**
76
- * Initiate required filters.
77
- *
78
- * @author Vova Feldman (@svovaf)
79
- * @since 1.0.4
80
- */
81
- private function filters() {
82
- // Override request for plugin information
83
- add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
84
-
85
- $this->add_transient_filters();
86
-
87
- /**
88
- * If user has the premium plugin's code but do NOT have an active license,
89
- * encourage him to upgrade by showing that there's a new release, but instead
90
- * of showing an update link, show upgrade link to the pricing page.
91
- *
92
- * @since 1.1.6
93
- *
94
- */
95
- // WP 2.9+
96
- add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
97
- &$this,
98
- 'catch_plugin_update_row'
99
- ), 9 );
100
- add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
101
- &$this,
102
- 'edit_and_echo_plugin_update_row'
103
- ), 11, 2 );
104
-
105
- if ( ! $this->_fs->has_any_active_valid_license() ) {
106
- add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) );
107
- }
108
-
109
- if ( ! WP_FS__IS_PRODUCTION_MODE ) {
110
- add_filter( 'http_request_host_is_external', array(
111
- $this,
112
- 'http_request_host_is_external_filter'
113
- ), 10, 3 );
114
- }
115
-
116
- if ( $this->_fs->is_premium() ) {
117
- if ( ! $this->is_correct_folder_name() ) {
118
- add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 );
119
- }
120
-
121
- add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 );
122
- add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
123
-
124
- if ( ! $this->_fs->has_any_active_valid_license() ) {
125
- add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 );
126
- }
127
- }
128
- }
129
-
130
- /**
131
- * @author Leo Fajardo (@leorw)
132
- * @since 2.1.4
133
- */
134
- function catch_plugin_information_dialog_contents() {
135
- if (
136
- 'plugin-information' !== fs_request_get( 'tab', false ) ||
137
- $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
138
- ) {
139
- return;
140
- }
141
-
142
- add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 );
143
-
144
- ob_start();
145
- }
146
-
147
- /**
148
- * @author Leo Fajardo (@leorw)
149
- * @since 2.1.4
150
- *
151
- * @param string $hook_suffix
152
- */
153
- function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) {
154
- if (
155
- 'plugin-information' !== fs_request_get( 'tab', false ) ||
156
- $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
157
- ) {
158
- return;
159
- }
160
-
161
- $license = $this->_fs->_get_license();
162
-
163
- $subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ?
164
- $this->_fs->_get_subscription( $license->id ) :
165
- null;
166
-
167
- $contents = ob_get_clean();
168
-
169
- $update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' );
170
-
171
- if ( false !== $update_button_id_attribute_pos ) {
172
- $update_button_start_pos = strrpos(
173
- substr( $contents, 0, $update_button_id_attribute_pos ),
174
- '<a'
175
- );
176
-
177
- $update_button_end_pos = ( strpos( $contents, '</a>', $update_button_id_attribute_pos ) + strlen( '</a>' ) );
178
-
179
- /**
180
- * The part of the contents without the update button.
181
- *
182
- * @author Leo Fajardo (@leorw)
183
- * @since 2.2.5
184
- */
185
- $modified_contents = substr( $contents, 0, $update_button_start_pos );
186
-
187
- $update_button = substr( $contents, $update_button_start_pos, ( $update_button_end_pos - $update_button_start_pos ) );
188
-
189
- /**
190
- * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
191
- * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
192
- * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page.
193
- */
194
- $update_button = preg_replace(
195
- '/(\<a.+)(id="plugin_update_from_iframe")(.+href=")([^\s]+)(".*\>)(.+)(\<\/a>)/is',
196
- is_object( $license ) ?
197
- sprintf(
198
- '$1$3%s$5%s$7',
199
- $this->_fs->checkout_url(
200
- is_object( $subscription ) ?
201
- ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) :
202
- WP_FS__PERIOD_LIFETIME,
203
- false,
204
- array( 'licenses' => $license->quota )
205
- ),
206
- fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() )
207
- ) :
208
- sprintf(
209
- '$1$3%s$5%s$7',
210
- $this->_fs->pricing_url(),
211
- fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() )
212
- ),
213
- $update_button
214
- );
215
-
216
- /**
217
- * Append the modified button.
218
- *
219
- * @author Leo Fajardo (@leorw)
220
- * @since 2.2.5
221
- */
222
- $modified_contents .= $update_button;
223
-
224
- /**
225
- * Append the remaining part of the contents after the update button.
226
- *
227
- * @author Leo Fajardo (@leorw)
228
- * @since 2.2.5
229
- */
230
- $modified_contents .= substr( $contents, $update_button_end_pos );
231
-
232
- $contents = $modified_contents;
233
- }
234
-
235
- echo $contents;
236
- }
237
-
238
- /**
239
- * @author Vova Feldman (@svovaf)
240
- * @since 2.0.0
241
- */
242
- private function add_transient_filters() {
243
- if ( $this->_fs->is_premium() && ! $this->_fs->is_tracking_allowed() ) {
244
- $this->_logger->log( 'Opted out sites cannot receive automatic software updates.' );
245
-
246
- return;
247
- }
248
-
249
- add_filter( 'pre_set_site_transient_update_plugins', array(
250
- &$this,
251
- 'pre_set_site_transient_update_plugins_filter'
252
- ) );
253
-
254
- add_filter( 'pre_set_site_transient_update_themes', array(
255
- &$this,
256
- 'pre_set_site_transient_update_plugins_filter'
257
- ) );
258
- }
259
-
260
- /**
261
- * @author Vova Feldman (@svovaf)
262
- * @since 2.0.0
263
- */
264
- private function remove_transient_filters() {
265
- remove_filter( 'pre_set_site_transient_update_plugins', array(
266
- &$this,
267
- 'pre_set_site_transient_update_plugins_filter'
268
- ) );
269
-
270
- remove_filter( 'pre_set_site_transient_update_themes', array(
271
- &$this,
272
- 'pre_set_site_transient_update_plugins_filter'
273
- ) );
274
- }
275
-
276
- /**
277
- * Capture plugin update row by turning output buffering.
278
- *
279
- * @author Vova Feldman (@svovaf)
280
- * @since 1.1.6
281
- */
282
- function catch_plugin_update_row() {
283
- ob_start();
284
- }
285
-
286
- /**
287
- * Overrides default update message format with "renew your license" message.
288
- *
289
- * @author Vova Feldman (@svovaf)
290
- * @since 1.1.6
291
- *
292
- * @param string $file
293
- * @param array $plugin_data
294
- */
295
- function edit_and_echo_plugin_update_row( $file, $plugin_data ) {
296
- $plugin_update_row = ob_get_clean();
297
-
298
- $current = get_site_transient( 'update_plugins' );
299
- if ( ! isset( $current->response[ $file ] ) ) {
300
- echo $plugin_update_row;
301
-
302
- return;
303
- }
304
-
305
- $r = $current->response[ $file ];
306
-
307
- $has_beta_update = $this->_fs->has_beta_update();
308
-
309
- if ( $this->_fs->has_any_active_valid_license() ) {
310
- if ( $has_beta_update ) {
311
- /**
312
- * Turn the "new version" text into "new Beta version".
313
- *
314
- * Sample input:
315
- * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
316
- * Output:
317
- * There is a new Beta version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
318
- *
319
- * @author Leo Fajardo (@leorw)
320
- * @since 2.3.0
321
- */
322
- $plugin_update_row = preg_replace(
323
- '/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
324
- (
325
- '$1' .
326
- sprintf(
327
- fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
328
- $has_beta_update ?
329
- fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
330
- fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ),
331
- $this->_fs->get_plugin_title()
332
- ) .
333
- ' ' .
334
- '$3' .
335
- '$6'
336
- ),
337
- $plugin_update_row
338
- );
339
- }
340
- } else {
341
- /**
342
- * Turn the "new version" text into a link that opens the plugin information dialog when clicked and
343
- * make the "View version x details" text link to the checkout page instead of opening the plugin
344
- * information dialog when clicked.
345
- *
346
- * Sample input:
347
- * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
348
- * Output:
349
- * There is a <a href="...>new version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
350
- * OR
351
- * There is a <a href="...>new Beta version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
352
- *
353
- * @author Leo Fajardo (@leorw)
354
- */
355
- $plugin_update_row = preg_replace(
356
- '/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
357
- (
358
- '$1' .
359
- sprintf(
360
- fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
361
- sprintf(
362
- '<a href="$4"%s>%s</a>',
363
- '$5',
364
- $has_beta_update ?
365
- fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
366
- fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() )
367
- ),
368
- $this->_fs->get_plugin_title()
369
- ) .
370
- ' ' .
371
- $this->_fs->version_upgrade_checkout_link( $r->new_version ) .
372
- '$6'
373
- ),
374
- $plugin_update_row
375
- );
376
- }
377
-
378
- if (
379
- $this->_fs->is_plugin() &&
380
- isset( $r->upgrade_notice ) &&
381
- strlen( trim( $r->upgrade_notice ) ) > 0
382
- ) {
383
- $slug = $this->_fs->get_slug();
384
-
385
- $upgrade_notice_html = sprintf(
386
- '<p class="notice fs-upgrade-notice fs-slug-%1$s fs-type-%2$s" data-slug="%1$s" data-type="%2$s"><strong>%3$s</strong> %4$s</p>',
387
- $slug,
388
- $this->_fs->get_module_type(),
389
- fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ),
390
- esc_html( $r->upgrade_notice )
391
- );
392
-
393
- $plugin_update_row = str_replace( '</div>', '</div>' . $upgrade_notice_html, $plugin_update_row );
394
- }
395
-
396
- echo $plugin_update_row;
397
- }
398
-
399
- /**
400
- * @author Leo Fajardo (@leorw)
401
- * @since 2.0.2
402
- *
403
- * @param array $prepared_themes
404
- *
405
- * @return array
406
- */
407
- function change_theme_update_info_html( $prepared_themes ) {
408
- $theme_basename = $this->_fs->get_plugin_basename();
409
-
410
- if ( ! isset( $prepared_themes[ $theme_basename ] ) ) {
411
- return $prepared_themes;
412
- }
413
-
414
- $themes_update = get_site_transient( 'update_themes' );
415
- if ( ! isset( $themes_update->response[ $theme_basename ] ) ||
416
- empty( $themes_update->response[ $theme_basename ]['package'] )
417
- ) {
418
- return $prepared_themes;
419
- }
420
-
421
- $prepared_themes[ $theme_basename ]['update'] = preg_replace(
422
- '/(\<p.+>)(.+)(\<a.+\<a.+)\.(.+\<\/p\>)/is',
423
- '$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) .
424
- '$4',
425
- $prepared_themes[ $theme_basename ]['update']
426
- );
427
-
428
- // Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page.
429
- $prepared_themes[ $theme_basename ]['hasPackage'] = false;
430
-
431
- return $prepared_themes;
432
- }
433
-
434
- /**
435
- * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip.
436
- * During development mode we want to be able updating plugin versions via our localhost repository. This
437
- * filter white-list all domains including "api.freemius".
438
- *
439
- * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/
440
- *
441
- * @author Vova Feldman (@svovaf)
442
- * @since 1.0.4
443
- *
444
- * @param bool $allow
445
- * @param string $host
446
- * @param string $url
447
- *
448
- * @return bool
449
- */
450
- function http_request_host_is_external_filter( $allow, $host, $url ) {
451
- return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow;
452
- }
453
-
454
- /**
455
- * Check for Updates at the defined API endpoint and modify the update array.
456
- *
457
- * This function dives into the update api just when WordPress creates its update array,
458
- * then adds a custom API call and injects the custom plugin data retrieved from the API.
459
- * It is reassembled from parts of the native WordPress plugin update code.
460
- * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
461
- *
462
- * @author Vova Feldman (@svovaf)
463
- * @since 1.0.4
464
- *
465
- * @uses FS_Api
466
- *
467
- * @param object $transient_data Update array build by WordPress.
468
- *
469
- * @return object Modified update array with custom plugin data.
470
- */
471
- function pre_set_site_transient_update_plugins_filter( $transient_data ) {
472
- $this->_logger->entrance();
473
-
474
- /**
475
- * "plugins" or "themes".
476
- *
477
- * @author Leo Fajardo (@leorw)
478
- * @since 1.2.2
479
- */
480
- $module_type = $this->_fs->get_module_type() . 's';
481
-
482
- /**
483
- * Ensure that we don't mix plugins update info with themes update info.
484
- *
485
- * @author Leo Fajardo (@leorw)
486
- * @since 1.2.2
487
- */
488
- if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) {
489
- return $transient_data;
490
- }
491
-
492
- if ( empty( $transient_data ) ||
493
- defined( 'WP_FS__UNINSTALL_MODE' )
494
- ) {
495
- return $transient_data;
496
- }
497
-
498
- global $wp_current_filter;
499
-
500
- $current_plugin_version = $this->_fs->get_plugin_version();
501
-
502
- if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) {
503
- if (
504
- is_null( $this->_update_details ) ||
505
- ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version )
506
- ) {
507
- /**
508
- * After an update, clear the stored update details and reparse the plugin's main file in order to get
509
- * the updated version's information and prevent the previous update information from showing up on the
510
- * updates page.
511
- *
512
- * @author Leo Fajardo (@leorw)
513
- * @since 2.3.1
514
- */
515
- $this->_update_details = null;
516
- $current_plugin_version = $this->_fs->get_plugin_version( true );
517
- }
518
- }
519
-
520
- if ( ! isset( $this->_update_details ) ) {
521
- // Get plugin's newest update.
522
- $new_version = $this->_fs->get_update(
523
- false,
524
- fs_request_get_bool( 'force-check' ),
525
- WP_FS__TIME_24_HOURS_IN_SEC / 24,
526
- $current_plugin_version
527
- );
528
-
529
- $this->_update_details = false;
530
-
531
- if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) {
532
- $this->_logger->log( 'Found newer plugin version ' . $new_version->version );
533
-
534
- /**
535
- * Cache plugin details locally since set_site_transient( 'update_plugins' )
536
- * called multiple times and the non wp.org plugins are filtered after the
537
- * call to .org.
538
- *
539
- * @since 1.1.8.1
540
- */
541
- $this->_update_details = $this->get_update_details( $new_version );
542
- }
543
- }
544
-
545
- // Alias.
546
- $basename = $this->_fs->premium_plugin_basename();
547
-
548
- if ( is_object( $this->_update_details ) ) {
549
- if ( isset( $transient_data->no_update ) ) {
550
- unset( $transient_data->no_update[ $basename ] );
551
- }
552
-
553
- if ( ! isset( $transient_data->response ) ) {
554
- $transient_data->response = array();
555
- }
556
-
557
- // Add plugin to transient data.
558
- $transient_data->response[ $basename ] = $this->_fs->is_plugin() ?
559
- $this->_update_details :
560
- (array) $this->_update_details;
561
- } else {
562
- if ( isset( $transient_data->response ) ) {
563
- /**
564
- * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version.
565
- *
566
- * @author Leo Fajardo (@leorw)
567
- * @since 2.3.0
568
- */
569
- unset( $transient_data->response[ $basename ] );
570
- }
571
-
572
- if ( ! isset( $transient_data->no_update ) ) {
573
- $transient_data->no_update = array();
574
- }
575
-
576
- /**
577
- * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI.
578
- *
579
- * @since 2.4.1
580
- * @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/
581
- */
582
- $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ?
583
- (object) array(
584
- 'id' => $basename,
585
- 'slug' => $this->_fs->get_slug(),
586
- 'plugin' => $basename,
587
- 'new_version' => $this->_fs->get_plugin_version(),
588
- 'url' => '',
589
- 'package' => '',
590
- 'icons' => array(),
591
- 'banners' => array(),
592
- 'banners_rtl' => array(),
593
- 'tested' => '',
594
- 'requires_php' => '',
595
- 'compatibility' => new stdClass(),
596
- ) :
597
- array(
598
- 'theme' => $basename,
599
- 'new_version' => $this->_fs->get_plugin_version(),
600
- 'url' => '',
601
- 'package' => '',
602
- 'requires' => '',
603
- 'requires_php' => '',
604
- );
605
- }
606
-
607
- $slug = $this->_fs->get_slug();
608
-
609
- if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) {
610
- if ( ! isset( $this->_translation_updates ) ) {
611
- $this->_translation_updates = array();
612
-
613
- if ( current_user_can( 'update_languages' ) ) {
614
- $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
615
- if ( ! empty( $translation_updates ) ) {
616
- $this->_translation_updates = $translation_updates;
617
- }
618
- }
619
- }
620
-
621
- if ( ! empty( $this->_translation_updates ) ) {
622
- $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ?
623
- $transient_data->translations :
624
- array();
625
-
626
- $current_plugin_translation_updates_map = array();
627
- foreach ( $all_translation_updates as $key => $translation_update ) {
628
- if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) {
629
- $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update;
630
- unset( $all_translation_updates[ $key ] );
631
- }
632
- }
633
-
634
- foreach ( $this->_translation_updates as $translation_update ) {
635
- $lang = $translation_update['language'];
636
- if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
637
- version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' )
638
- ) {
639
- $current_plugin_translation_updates_map[ $lang ] = $translation_update;
640
- }
641
- }
642
-
643
- $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) );
644
- }
645
- }
646
-
647
- return $transient_data;
648
- }
649
-
650
- /**
651
- * Get module's required data for the updates mechanism.
652
- *
653
- * @author Vova Feldman (@svovaf)
654
- * @since 2.0.0
655
- *
656
- * @param \FS_Plugin_Tag $new_version
657
- *
658
- * @return object
659
- */
660
- function get_update_details( FS_Plugin_Tag $new_version ) {
661
- $update = new stdClass();
662
- $update->slug = $this->_fs->get_slug();
663
- $update->new_version = $new_version->version;
664
- $update->url = WP_FS__ADDRESS;
665
- $update->package = $new_version->url;
666
- $update->tested = $new_version->tested_up_to_version;
667
- $update->requires = $new_version->requires_platform_version;
668
-
669
- $icon = $this->_fs->get_local_icon_url();
670
-
671
- if ( ! empty( $icon ) ) {
672
- $update->icons = array(
673
- // '1x' => $icon,
674
- // '2x' => $icon,
675
- 'default' => $icon,
676
- );
677
- }
678
-
679
- if ( $this->_fs->is_premium() ) {
680
- $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
681
-
682
- if (
683
- isset( $latest_tag->readme ) &&
684
- isset( $latest_tag->readme->upgrade_notice ) &&
685
- ! empty( $latest_tag->readme->upgrade_notice )
686
- ) {
687
- $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
688
- }
689
- }
690
-
691
- $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
692
-
693
- return $update;
694
- }
695
-
696
- /**
697
- * @author Leo Fajardo (@leorw)
698
- * @since 2.3.0
699
- *
700
- * @param FS_Plugin_Tag $new_version
701
- *
702
- * @return bool
703
- */
704
- private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
705
- $query_str = parse_url( $new_version->url, PHP_URL_QUERY );
706
- if ( empty( $query_str ) ) {
707
- return false;
708
- }
709
-
710
- parse_str( $query_str, $params );
711
-
712
- return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
713
- }
714
-
715
- /**
716
- * Update the updates transient with the module's update information.
717
- *
718
- * This method is required for multisite environment.
719
- * If a module is site activated (not network) and not on the main site,
720
- * the module will NOT be executed on the network level, therefore, the
721
- * custom updates logic will not be executed as well, so unless we force
722
- * the injection of the update into the updates transient, premium updates
723
- * will not work.
724
- *
725
- * @author Vova Feldman (@svovaf)
726
- * @since 2.0.0
727
- *
728
- * @param \FS_Plugin_Tag $new_version
729
- */
730
- function set_update_data( FS_Plugin_Tag $new_version ) {
731
- $this->_logger->entrance();
732
-
733
- if ( ! $this->is_new_version_premium( $new_version ) ) {
734
- return;
735
- }
736
-
737
- $transient_key = "update_{$this->_fs->get_module_type()}s";
738
-
739
- $transient_data = get_site_transient( $transient_key );
740
-
741
- $transient_data = is_object( $transient_data ) ?
742
- $transient_data :
743
- new stdClass();
744
-
745
- // Alias.
746
- $basename = $this->_fs->get_plugin_basename();
747
- $is_plugin = $this->_fs->is_plugin();
748
-
749
- if ( ! isset( $transient_data->response ) ||
750
- ! is_array( $transient_data->response )
751
- ) {
752
- $transient_data->response = array();
753
- } else if ( ! empty( $transient_data->response[ $basename ] ) ) {
754
- $version = $is_plugin ?
755
- ( ! empty( $transient_data->response[ $basename ]->new_version ) ?
756
- $transient_data->response[ $basename ]->new_version :
757
- null
758
- ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ?
759
- $transient_data->response[ $basename ]['new_version'] :
760
- null
761
- );
762
-
763
- if ( $version == $new_version->version ) {
764
- // The update data is already set.
765
- return;
766
- }
767
- }
768
-
769
- // Remove the added filters.
770
- $this->remove_transient_filters();
771
-
772
- $this->_update_details = $this->get_update_details( $new_version );
773
-
774
- // Set update data in transient.
775
- $transient_data->response[ $basename ] = $is_plugin ?
776
- $this->_update_details :
777
- (array) $this->_update_details;
778
-
779
- if ( ! isset( $transient_data->checked ) ||
780
- ! is_array( $transient_data->checked )
781
- ) {
782
- $transient_data->checked = array();
783
- }
784
-
785
- // Flag the module as if it was already checked.
786
- $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version();
787
- $transient_data->last_checked = time();
788
-
789
- set_site_transient( $transient_key, $transient_data );
790
-
791
- $this->add_transient_filters();
792
- }
793
-
794
- /**
795
- * @author Leo Fajardo (@leorw)
796
- * @since 2.0.2
797
- */
798
- function delete_update_data() {
799
- $this->_logger->entrance();
800
-
801
- $transient_key = "update_{$this->_fs->get_module_type()}s";
802
-
803
- $transient_data = get_site_transient( $transient_key );
804
-
805
- // Alias
806
- $basename = $this->_fs->get_plugin_basename();
807
-
808
- if ( ! is_object( $transient_data ) ||
809
- ! isset( $transient_data->response ) ||
810
- ! is_array( $transient_data->response ) ||
811
- empty( $transient_data->response[ $basename ] )
812
- ) {
813
- return;
814
- }
815
-
816
- unset( $transient_data->response[ $basename ] );
817
-
818
- // Remove the added filters.
819
- $this->remove_transient_filters();
820
-
821
- set_site_transient( $transient_key, $transient_data );
822
-
823
- $this->add_transient_filters();
824
- }
825
-
826
- /**
827
- * Try to fetch plugin's info from .org repository.
828
- *
829
- * @author Vova Feldman (@svovaf)
830
- * @since 1.0.5
831
- *
832
- * @param string $action
833
- * @param object $args
834
- *
835
- * @return bool|mixed
836
- */
837
- static function _fetch_plugin_info_from_repository( $action, $args ) {
838
- $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
839
- if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
840
- $url = set_url_scheme( $url, 'https' );
841
- }
842
-
843
- $args = array(
844
- 'timeout' => 15,
845
- 'body' => array(
846
- 'action' => $action,
847
- 'request' => serialize( $args )
848
- )
849
- );
850
-
851
- $request = wp_remote_post( $url, $args );
852
-
853
- if ( is_wp_error( $request ) ) {
854
- return false;
855
- }
856
-
857
- $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
858
-
859
- if ( ! is_object( $res ) && ! is_array( $res ) ) {
860
- return false;
861
- }
862
-
863
- return $res;
864
- }
865
-
866
- /**
867
- * Fetches module translation updates from wordpress.org.
868
- *
869
- * @author Leo Fajardo (@leorw)
870
- * @since 2.1.2
871
- *
872
- * @param string $module_type
873
- * @param string $slug
874
- *
875
- * @return array|null
876
- */
877
- private function fetch_wp_org_module_translation_updates( $module_type, $slug ) {
878
- $plugin_data = $this->_fs->get_plugin_data();
879
-
880
- $locales = array_values( get_available_languages() );
881
- $locales = apply_filters( "{$module_type}_update_check_locales", $locales );
882
- $locales = array_unique( $locales );
883
-
884
- $plugin_basename = $this->_fs->get_plugin_basename();
885
- if ( 'themes' === $module_type ) {
886
- $plugin_basename = $slug;
887
- }
888
-
889
- global $wp_version;
890
-
891
- $request_args = array(
892
- 'timeout' => 15,
893
- 'body' => array(
894
- "{$module_type}" => json_encode(
895
- array(
896
- "{$module_type}" => array(
897
- $plugin_basename => array(
898
- 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ),
899
- 'Author' => $plugin_data['Author'],
900
- )
901
- )
902
- )
903
- ),
904
- 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ),
905
- 'locale' => json_encode( $locales )
906
- ),
907
- 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) )
908
- );
909
-
910
- $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/";
911
- if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
912
- $url = set_url_scheme( $url, 'https' );
913
- }
914
-
915
- $raw_response = Freemius::safe_remote_post(
916
- $url,
917
- $request_args,
918
- WP_FS__TIME_24_HOURS_IN_SEC,
919
- WP_FS__TIME_12_HOURS_IN_SEC,
920
- false
921
- );
922
-
923
- if ( is_wp_error( $raw_response ) ) {
924
- return null;
925
- }
926
-
927
- $response = json_decode( wp_remote_retrieve_body( $raw_response ), true );
928
-
929
- if ( ! is_array( $response ) ) {
930
- return null;
931
- }
932
-
933
- if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) {
934
- return null;
935
- }
936
-
937
- return $response['translations'];
938
- }
939
-
940
- /**
941
- * @author Leo Fajardo (@leorw)
942
- * @since 2.1.2
943
- *
944
- * @param string $module_type
945
- * @param string $slug
946
- *
947
- * @return array
948
- */
949
- private function get_installed_translations( $module_type, $slug ) {
950
- if ( function_exists( 'wp_get_installed_translations' ) ) {
951
- return wp_get_installed_translations( $module_type );
952
- }
953
-
954
- $dir = "/{$module_type}";
955
-
956
- if ( ! is_dir( WP_LANG_DIR . $dir ) )
957
- return array();
958
-
959
- $files = scandir( WP_LANG_DIR . $dir );
960
- if ( ! $files )
961
- return array();
962
-
963
- $language_data = array();
964
-
965
- foreach ( $files as $file ) {
966
- if ( 0 !== strpos( $file, $slug ) ) {
967
- continue;
968
- }
969
-
970
- if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) {
971
- continue;
972
- }
973
-
974
- if ( substr( $file, -3 ) !== '.po' ) {
975
- continue;
976
- }
977
-
978
- if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) {
979
- continue;
980
- }
981
-
982
- if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) {
983
- continue;
984
- }
985
-
986
- list( , $textdomain, $language ) = $match;
987
-
988
- if ( '' === $textdomain ) {
989
- $textdomain = 'default';
990
- }
991
-
992
- $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" );
993
- }
994
-
995
- return $language_data;
996
- }
997
-
998
- /**
999
- * Updates information on the "View version x.x details" page with custom data.
1000
- *
1001
- * @author Vova Feldman (@svovaf)
1002
- * @since 1.0.4
1003
- *
1004
- * @uses FS_Api
1005
- *
1006
- * @param object $data
1007
- * @param string $action
1008
- * @param mixed $args
1009
- *
1010
- * @return object
1011
- */
1012
- function plugins_api_filter( $data, $action = '', $args = null ) {
1013
- $this->_logger->entrance();
1014
-
1015
- if ( ( 'plugin_information' !== $action ) ||
1016
- ! isset( $args->slug )
1017
- ) {
1018
- return $data;
1019
- }
1020
-
1021
- $addon = false;
1022
- $is_addon = false;
1023
- $addon_version = false;
1024
-
1025
- if ( $this->_fs->get_slug() !== $args->slug ) {
1026
- $addon = $this->_fs->get_addon_by_slug( $args->slug );
1027
-
1028
- if ( ! is_object( $addon ) ) {
1029
- return $data;
1030
- }
1031
-
1032
- if ( $this->_fs->is_addon_activated( $addon->id ) ) {
1033
- $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version();
1034
- } else if ( $this->_fs->is_addon_installed( $addon->id ) ) {
1035
- $addon_plugin_data = get_plugin_data(
1036
- ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ),
1037
- false,
1038
- false
1039
- );
1040
-
1041
- if ( ! empty( $addon_plugin_data ) ) {
1042
- $addon_version = $addon_plugin_data['Version'];
1043
- }
1044
- }
1045
-
1046
- $is_addon = true;
1047
- }
1048
-
1049
- $plugin_in_repo = false;
1050
- if ( ! $is_addon ) {
1051
- // Try to fetch info from .org repository.
1052
- $data = self::_fetch_plugin_info_from_repository( $action, $args );
1053
-
1054
- $plugin_in_repo = ( false !== $data );
1055
- }
1056
-
1057
- if ( ! $plugin_in_repo ) {
1058
- $data = $args;
1059
-
1060
- // Fetch as much as possible info from local files.
1061
- $plugin_local_data = $this->_fs->get_plugin_data();
1062
- $data->name = $plugin_local_data['Name'];
1063
- $data->author = $plugin_local_data['Author'];
1064
- $data->sections = array(
1065
- 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.',
1066
- );
1067
-
1068
- // @todo Store extra plugin info on Freemius or parse readme.txt markup.
1069
- /*$info = $this->_fs->get_api_site_scope()->call('/information.json');
1070
-
1071
- if ( !isset($info->error) ) {
1072
- $data = $info;
1073
- }*/
1074
- }
1075
-
1076
- $plugin_version = $is_addon ?
1077
- $addon_version :
1078
- $this->_fs->get_plugin_version();
1079
-
1080
- // Get plugin's newest update.
1081
- $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version );
1082
-
1083
- if ( ! is_object( $new_version ) || empty( $new_version->version ) ) {
1084
- $data->version = $plugin_version;
1085
- } else {
1086
- if ( $is_addon ) {
1087
- $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1088
- $data->slug = $addon->slug;
1089
- $data->url = WP_FS__ADDRESS;
1090
- $data->package = $new_version->url;
1091
- }
1092
-
1093
- if ( ! $plugin_in_repo ) {
1094
- $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
1095
- $data->requires = $new_version->requires_platform_version;
1096
- $data->tested = $new_version->tested_up_to_version;
1097
- }
1098
-
1099
- $data->version = $new_version->version;
1100
- $data->download_link = $new_version->url;
1101
-
1102
- if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) {
1103
- $new_version_readme_data = $new_version->readme;
1104
- if ( isset( $new_version_readme_data->sections ) ) {
1105
- $new_version_readme_data->sections = (array) $new_version_readme_data->sections;
1106
- } else {
1107
- $new_version_readme_data->sections = array();
1108
- }
1109
-
1110
- if ( isset( $data->sections ) ) {
1111
- if ( isset( $data->sections['screenshots'] ) ) {
1112
- $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots'];
1113
- }
1114
-
1115
- if ( isset( $data->sections['reviews'] ) ) {
1116
- $new_version_readme_data->sections['reviews'] = $data->sections['reviews'];
1117
- }
1118
- }
1119
-
1120
- if ( isset( $new_version_readme_data->banners ) ) {
1121
- $new_version_readme_data->banners = (array) $new_version_readme_data->banners;
1122
- } else if ( isset( $data->banners ) ) {
1123
- $new_version_readme_data->banners = $data->banners;
1124
- }
1125
-
1126
- $wp_org_sections = array(
1127
- 'author',
1128
- 'author_profile',
1129
- 'rating',
1130
- 'ratings',
1131
- 'num_ratings',
1132
- 'support_threads',
1133
- 'support_threads_resolved',
1134
- 'active_installs',
1135
- 'added',
1136
- 'homepage'
1137
- );
1138
-
1139
- foreach ( $wp_org_sections as $wp_org_section ) {
1140
- if ( isset( $data->{$wp_org_section} ) ) {
1141
- $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section};
1142
- }
1143
- }
1144
-
1145
- $data = $new_version_readme_data;
1146
- }
1147
- }
1148
-
1149
- return $data;
1150
- }
1151
-
1152
- /**
1153
- * @author Vova Feldman (@svovaf)
1154
- * @since 1.2.1.7
1155
- *
1156
- * @param number|bool $addon_id
1157
- * @param bool|string $newer_than Since 2.2.1
1158
- * @param bool|string $fetch_readme Since 2.2.1
1159
- *
1160
- * @return object
1161
- */
1162
- private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1163
- return $this->_fs->_fetch_latest_version( $addon_id, true, WP_FS__TIME_24_HOURS_IN_SEC, $newer_than, $fetch_readme );
1164
- }
1165
-
1166
- /**
1167
- * Checks if a given basename has a matching folder name
1168
- * with the current context plugin.
1169
- *
1170
- * @author Vova Feldman (@svovaf)
1171
- * @since 1.2.1.6
1172
- *
1173
- * @return bool
1174
- */
1175
- private function is_correct_folder_name() {
1176
- return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) );
1177
- }
1178
-
1179
- /**
1180
- * This is a special after upgrade handler for migrating modules
1181
- * that didn't use the '-premium' suffix folder structure before
1182
- * the migration.
1183
- *
1184
- * @author Vova Feldman (@svovaf)
1185
- * @since 1.2.1.6
1186
- *
1187
- * @param bool $response Install response.
1188
- * @param array $hook_extra Extra arguments passed to hooked filters.
1189
- * @param array $result Installation result data.
1190
- *
1191
- * @return bool
1192
- */
1193
- function _maybe_update_folder_name( $response, $hook_extra, $result ) {
1194
- $basename = $this->_fs->get_plugin_basename();
1195
-
1196
- if ( true !== $response ||
1197
- empty( $hook_extra ) ||
1198
- empty( $hook_extra['plugin'] ) ||
1199
- $basename !== $hook_extra['plugin']
1200
- ) {
1201
- return $response;
1202
- }
1203
-
1204
- $active_plugins_basenames = get_option( 'active_plugins' );
1205
-
1206
- foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) {
1207
- if ( $basename === $active_plugin_basename ) {
1208
- // Get filename including extension.
1209
- $filename = basename( $basename );
1210
-
1211
- $new_basename = plugin_basename(
1212
- trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) .
1213
- $filename
1214
- );
1215
-
1216
- // Verify that the expected correct path exists.
1217
- if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) {
1218
- // Override active plugin name.
1219
- $active_plugins_basenames[ $key ] = $new_basename;
1220
- update_option( 'active_plugins', $active_plugins_basenames );
1221
- }
1222
-
1223
- break;
1224
- }
1225
- }
1226
-
1227
- return $response;
1228
- }
1229
-
1230
- #----------------------------------------------------------------------------------
1231
- #region Auto Activation
1232
- #----------------------------------------------------------------------------------
1233
-
1234
- /**
1235
- * Installs and active a plugin when explicitly requested that from a 3rd party service.
1236
- *
1237
- * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin.
1238
- *
1239
- * @link http://tgmpluginactivation.com/
1240
- *
1241
- * @author Vova Feldman
1242
- * @since 1.2.1.7
1243
- *
1244
- * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
1245
- *
1246
- * @uses WP_Filesystem
1247
- * @uses WP_Error
1248
- * @uses WP_Upgrader
1249
- * @uses Plugin_Upgrader
1250
- * @uses Plugin_Installer_Skin
1251
- * @uses Plugin_Upgrader_Skin
1252
- *
1253
- * @param number|bool $plugin_id
1254
- *
1255
- * @return array
1256
- */
1257
- function install_and_activate_plugin( $plugin_id = false ) {
1258
- if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) {
1259
- // Invalid plugin ID.
1260
- return array(
1261
- 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1262
- 'code' => 'invalid_module_id',
1263
- );
1264
- }
1265
-
1266
- $is_addon = false;
1267
- if ( FS_Plugin::is_valid_id( $plugin_id ) &&
1268
- $plugin_id != $this->_fs->get_id()
1269
- ) {
1270
- $addon = $this->_fs->get_addon( $plugin_id );
1271
-
1272
- if ( ! is_object( $addon ) ) {
1273
- // Invalid add-on ID.
1274
- return array(
1275
- 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1276
- 'code' => 'invalid_module_id',
1277
- );
1278
- }
1279
-
1280
- $slug = $addon->slug;
1281
- $premium_slug = $addon->premium_slug;
1282
- $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1283
-
1284
- $is_addon = true;
1285
- } else {
1286
- $slug = $this->_fs->get_slug();
1287
- $premium_slug = $this->_fs->get_premium_slug();
1288
- $title = $this->_fs->get_plugin_title() .
1289
- ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' );
1290
- }
1291
-
1292
- if ( $this->is_premium_plugin_active( $plugin_id ) ) {
1293
- // Premium version already activated.
1294
- return array(
1295
- 'message' => $is_addon ?
1296
- $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) :
1297
- $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ),
1298
- 'code' => 'premium_installed',
1299
- );
1300
- }
1301
-
1302
- $latest_version = $this->get_latest_download_details( $plugin_id, false, false );
1303
- $target_folder = $premium_slug;
1304
-
1305
- // Prep variables for Plugin_Installer_Skin class.
1306
- $extra = array();
1307
- $extra['slug'] = $target_folder;
1308
- $source = $latest_version->url;
1309
- $api = null;
1310
-
1311
- $install_url = add_query_arg(
1312
- array(
1313
- 'action' => 'install-plugin',
1314
- 'plugin' => urlencode( $slug ),
1315
- ),
1316
- 'update.php'
1317
- );
1318
-
1319
- if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
1320
- // Include required resources for the installation.
1321
- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1322
- }
1323
-
1324
- $skin_args = array(
1325
- 'type' => 'web',
1326
- 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ),
1327
- 'url' => esc_url_raw( $install_url ),
1328
- 'nonce' => 'install-plugin_' . $slug,
1329
- 'plugin' => '',
1330
- 'api' => $api,
1331
- 'extra' => $extra,
1332
- );
1333
-
1334
- // $skin = new Automatic_Upgrader_Skin( $skin_args );
1335
- // $skin = new Plugin_Installer_Skin( $skin_args );
1336
- $skin = new WP_Ajax_Upgrader_Skin( $skin_args );
1337
-
1338
- // Create a new instance of Plugin_Upgrader.
1339
- $upgrader = new Plugin_Upgrader( $skin );
1340
-
1341
- // Perform the action and install the plugin from the $source urldecode().
1342
- add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
1343
-
1344
- $install_result = $upgrader->install( $source );
1345
-
1346
- remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 );
1347
-
1348
- if ( is_wp_error( $install_result ) ) {
1349
- return array(
1350
- 'message' => $install_result->get_error_message(),
1351
- 'code' => $install_result->get_error_code(),
1352
- );
1353
- } elseif ( is_wp_error( $skin->result ) ) {
1354
- return array(
1355
- 'message' => $skin->result->get_error_message(),
1356
- 'code' => $skin->result->get_error_code(),
1357
- );
1358
- } elseif ( $skin->get_errors()->get_error_code() ) {
1359
- return array(
1360
- 'message' => $skin->get_error_messages(),
1361
- 'code' => 'unknown',
1362
- );
1363
- } elseif ( is_null( $install_result ) ) {
1364
- global $wp_filesystem;
1365
-
1366
- $error_code = 'unable_to_connect_to_filesystem';
1367
- $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' );
1368
-
1369
- // Pass through the error from WP_Filesystem if one was raised.
1370
- if ( $wp_filesystem instanceof WP_Filesystem_Base &&
1371
- is_wp_error( $wp_filesystem->errors ) &&
1372
- $wp_filesystem->errors->get_error_code()
1373
- ) {
1374
- $error_message = $wp_filesystem->errors->get_error_message();
1375
- }
1376
-
1377
- return array(
1378
- 'message' => $error_message,
1379
- 'code' => $error_code,
1380
- );
1381
- }
1382
-
1383
- // Grab the full path to the main plugin's file.
1384
- $plugin_activate = $upgrader->plugin_info();
1385
-
1386
- // Try to activate the plugin.
1387
- $activation_result = $this->try_activate_plugin( $plugin_activate );
1388
-
1389
- if ( is_wp_error( $activation_result ) ) {
1390
- return array(
1391
- 'message' => $activation_result->get_error_message(),
1392
- 'code' => $activation_result->get_error_code(),
1393
- );
1394
- }
1395
-
1396
- return $skin->get_upgrade_messages();
1397
- }
1398
-
1399
- /**
1400
- * Tries to activate a plugin. If fails, returns the error.
1401
- *
1402
- * @author Vova Feldman
1403
- * @since 1.2.1.7
1404
- *
1405
- * @param string $file_path Path within wp-plugins/ to main plugin file.
1406
- * This determines the styling of the output messages.
1407
- *
1408
- * @return bool|WP_Error
1409
- */
1410
- protected function try_activate_plugin( $file_path ) {
1411
- $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() );
1412
-
1413
- return is_wp_error( $activate ) ?
1414
- $activate :
1415
- true;
1416
- }
1417
-
1418
- /**
1419
- * Check if a premium module version is already active.
1420
- *
1421
- * @author Vova Feldman
1422
- * @since 1.2.1.7
1423
- *
1424
- * @param number|bool $plugin_id
1425
- *
1426
- * @return bool
1427
- */
1428
- private function is_premium_plugin_active( $plugin_id = false ) {
1429
- if ( $plugin_id != $this->_fs->get_id() ) {
1430
- return $this->_fs->is_addon_activated( $plugin_id, true );
1431
- }
1432
-
1433
- return is_plugin_active( $this->_fs->premium_plugin_basename() );
1434
- }
1435
-
1436
- /**
1437
- * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below.
1438
- *
1439
- * @author Leo Fajardo (@leorw)
1440
- * @since 2.2.1
1441
- *
1442
- * @param bool|WP_Error $response Response.
1443
- * @param array $hook_extra Extra arguments passed to hooked filters.
1444
- *
1445
- * @return bool|WP_Error
1446
- */
1447
- static function _store_basename_for_source_adjustment( $response, $hook_extra ) {
1448
- if ( isset( $hook_extra['plugin'] ) ) {
1449
- self::$_upgrade_basename = $hook_extra['plugin'];
1450
- } else if ( isset( $hook_extra['theme'] ) ) {
1451
- self::$_upgrade_basename = $hook_extra['theme'];
1452
- } else {
1453
- self::$_upgrade_basename = null;
1454
- }
1455
-
1456
- return $response;
1457
- }
1458
-
1459
- /**
1460
- * Adjust the plugin directory name if necessary.
1461
- * Assumes plugin has a folder (not a single file plugin).
1462
- *
1463
- * The final destination directory of a plugin is based on the subdirectory name found in the
1464
- * (un)zipped source. In some cases this subdirectory name is not the same as the expected
1465
- * slug and the plugin will not be recognized as installed. This is fixed by adjusting
1466
- * the temporary unzipped source subdirectory name to the expected plugin slug.
1467
- *
1468
- * @author Vova Feldman
1469
- * @since 1.2.1.7
1470
- * @since 2.2.1 The method was converted to static since when the admin update bulk products via the Updates section, the logic applies the `upgrader_source_selection` filter for every product that is being updated.
1471
- *
1472
- * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/.
1473
- * @param string $remote_source Path to upgrade/zip-file-name.tmp.
1474
- * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin.
1475
- *
1476
- * @return string|WP_Error
1477
- */
1478
- static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) {
1479
- if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) {
1480
- return $source;
1481
- }
1482
-
1483
- $basename = self::$_upgrade_basename;
1484
- $is_theme = false;
1485
-
1486
- // Figure out what the slug is supposed to be.
1487
- if ( isset( $upgrader->skin->options['extra'] ) ) {
1488
- // Set by the auto-install logic.
1489
- $desired_slug = $upgrader->skin->options['extra']['slug'];
1490
- } else if ( ! empty( $basename ) ) {
1491
- /**
1492
- * If it doesn't end with ".php", it's a theme.
1493
- *
1494
- * @author Leo Fajardo (@leorw)
1495
- * @since 2.2.1
1496
- */
1497
- $is_theme = ( ! fs_ends_with( $basename, '.php' ) );
1498
-
1499
- $desired_slug = ( ! $is_theme ) ?
1500
- dirname( $basename ) :
1501
- // Theme slug
1502
- $basename;
1503
- } else {
1504
- // Can't figure out the desired slug, stop the execution.
1505
- return $source;
1506
- }
1507
-
1508
- if ( is_multisite() ) {
1509
- /**
1510
- * If we are running in a multisite environment and the product is not network activated,
1511
- * the instance will not exist anyway. Therefore, try to update the source if necessary
1512
- * regardless if the Freemius instance of the product exists or not.
1513
- *
1514
- * @author Vova Feldman
1515
- */
1516
- } else if ( ! empty( $basename ) ) {
1517
- $fs = Freemius::get_instance_by_file(
1518
- $basename,
1519
- $is_theme ?
1520
- WP_FS__MODULE_TYPE_THEME :
1521
- WP_FS__MODULE_TYPE_PLUGIN
1522
- );
1523
-
1524
- if ( ! is_object( $fs ) ) {
1525
- /**
1526
- * If the Freemius instance does not exist on a non-multisite network environment, it means that:
1527
- * 1. The product is not powered by Freemius; OR
1528
- * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change.
1529
- *
1530
- * @author Leo Fajardo (@leorw)
1531
- * @since 2.2.1
1532
- */
1533
- return $source;
1534
- }
1535
- }
1536
-
1537
- $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) );
1538
-
1539
- if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) {
1540
- $from_path = untrailingslashit( $source );
1541
- $to_path = trailingslashit( $remote_source ) . $desired_slug;
1542
-
1543
- if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) {
1544
- return trailingslashit( $to_path );
1545
- }
1546
-
1547
- return new WP_Error(
1548
- 'rename_failed',
1549
- fs_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ),
1550
- array(
1551
- 'found' => $subdir_name,
1552
- 'expected' => $desired_slug
1553
- )
1554
- );
1555
- }
1556
-
1557
- return $source;
1558
- }
1559
-
1560
- #endregion
1561
- }
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.0.4
7
+ *
8
+ * @link https://github.com/easydigitaldownloads/EDD-License-handler/blob/master/EDD_SL_Plugin_Updater.php
9
+ */
10
+
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
+ }
14
+
15
+ class FS_Plugin_Updater {
16
+
17
+ /**
18
+ * @var Freemius
19
+ * @since 1.0.4
20
+ */
21
+ private $_fs;
22
+ /**
23
+ * @var FS_Logger
24
+ * @since 1.0.4
25
+ */
26
+ private $_logger;
27
+ /**
28
+ * @var object
29
+ * @since 1.1.8.1
30
+ */
31
+ private $_update_details;
32
+ /**
33
+ * @var array
34
+ * @since 2.1.2
35
+ */
36
+ private $_translation_updates;
37
+
38
+ private static $_upgrade_basename = null;
39
+
40
+ #--------------------------------------------------------------------------------
41
+ #region Singleton
42
+ #--------------------------------------------------------------------------------
43
+
44
+ /**
45
+ * @var FS_Plugin_Updater[]
46
+ * @since 2.0.0
47
+ */
48
+ private static $_INSTANCES = array();
49
+
50
+ /**
51
+ * @param Freemius $freemius
52
+ *
53
+ * @return FS_Plugin_Updater
54
+ */
55
+ static function instance( Freemius $freemius ) {
56
+ $key = $freemius->get_id();
57
+
58
+ if ( ! isset( self::$_INSTANCES[ $key ] ) ) {
59
+ self::$_INSTANCES[ $key ] = new self( $freemius );
60
+ }
61
+
62
+ return self::$_INSTANCES[ $key ];
63
+ }
64
+
65
+ #endregion
66
+
67
+ private function __construct( Freemius $freemius ) {
68
+ $this->_fs = $freemius;
69
+
70
+ $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
71
+
72
+ $this->filters();
73
+ }
74
+
75
+ /**
76
+ * Initiate required filters.
77
+ *
78
+ * @author Vova Feldman (@svovaf)
79
+ * @since 1.0.4
80
+ */
81
+ private function filters() {
82
+ // Override request for plugin information
83
+ add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
84
+
85
+ $this->add_transient_filters();
86
+
87
+ /**
88
+ * If user has the premium plugin's code but do NOT have an active license,
89
+ * encourage him to upgrade by showing that there's a new release, but instead
90
+ * of showing an update link, show upgrade link to the pricing page.
91
+ *
92
+ * @since 1.1.6
93
+ *
94
+ */
95
+ // WP 2.9+
96
+ add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
97
+ &$this,
98
+ 'catch_plugin_update_row'
99
+ ), 9 );
100
+ add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
101
+ &$this,
102
+ 'edit_and_echo_plugin_update_row'
103
+ ), 11, 2 );
104
+
105
+ if ( ! $this->_fs->has_any_active_valid_license() ) {
106
+ add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) );
107
+ }
108
+
109
+ if ( ! WP_FS__IS_PRODUCTION_MODE ) {
110
+ add_filter( 'http_request_host_is_external', array(
111
+ $this,
112
+ 'http_request_host_is_external_filter'
113
+ ), 10, 3 );
114
+ }
115
+
116
+ if ( $this->_fs->is_premium() ) {
117
+ if ( ! $this->is_correct_folder_name() ) {
118
+ add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 );
119
+ }
120
+
121
+ add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 );
122
+ add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
123
+
124
+ if ( ! $this->_fs->has_any_active_valid_license() ) {
125
+ add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 );
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * @author Leo Fajardo (@leorw)
132
+ * @since 2.1.4
133
+ */
134
+ function catch_plugin_information_dialog_contents() {
135
+ if (
136
+ 'plugin-information' !== fs_request_get( 'tab', false ) ||
137
+ $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
138
+ ) {
139
+ return;
140
+ }
141
+
142
+ add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 );
143
+
144
+ ob_start();
145
+ }
146
+
147
+ /**
148
+ * @author Leo Fajardo (@leorw)
149
+ * @since 2.1.4
150
+ *
151
+ * @param string $hook_suffix
152
+ */
153
+ function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) {
154
+ if (
155
+ 'plugin-information' !== fs_request_get( 'tab', false ) ||
156
+ $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
157
+ ) {
158
+ return;
159
+ }
160
+
161
+ $license = $this->_fs->_get_license();
162
+
163
+ $subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ?
164
+ $this->_fs->_get_subscription( $license->id ) :
165
+ null;
166
+
167
+ $contents = ob_get_clean();
168
+
169
+ $update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' );
170
+
171
+ if ( false !== $update_button_id_attribute_pos ) {
172
+ $update_button_start_pos = strrpos(
173
+ substr( $contents, 0, $update_button_id_attribute_pos ),
174
+ '<a'
175
+ );
176
+
177
+ $update_button_end_pos = ( strpos( $contents, '</a>', $update_button_id_attribute_pos ) + strlen( '</a>' ) );
178
+
179
+ /**
180
+ * The part of the contents without the update button.
181
+ *
182
+ * @author Leo Fajardo (@leorw)
183
+ * @since 2.2.5
184
+ */
185
+ $modified_contents = substr( $contents, 0, $update_button_start_pos );
186
+
187
+ $update_button = substr( $contents, $update_button_start_pos, ( $update_button_end_pos - $update_button_start_pos ) );
188
+
189
+ /**
190
+ * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
191
+ * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
192
+ * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page.
193
+ */
194
+ $update_button = preg_replace(
195
+ '/(\<a.+)(id="plugin_update_from_iframe")(.+href=")([^\s]+)(".*\>)(.+)(\<\/a>)/is',
196
+ is_object( $license ) ?
197
+ sprintf(
198
+ '$1$3%s$5%s$7',
199
+ $this->_fs->checkout_url(
200
+ is_object( $subscription ) ?
201
+ ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) :
202
+ WP_FS__PERIOD_LIFETIME,
203
+ false,
204
+ array( 'licenses' => $license->quota )
205
+ ),
206
+ fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() )
207
+ ) :
208
+ sprintf(
209
+ '$1$3%s$5%s$7',
210
+ $this->_fs->pricing_url(),
211
+ fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() )
212
+ ),
213
+ $update_button
214
+ );
215
+
216
+ /**
217
+ * Append the modified button.
218
+ *
219
+ * @author Leo Fajardo (@leorw)
220
+ * @since 2.2.5
221
+ */
222
+ $modified_contents .= $update_button;
223
+
224
+ /**
225
+ * Append the remaining part of the contents after the update button.
226
+ *
227
+ * @author Leo Fajardo (@leorw)
228
+ * @since 2.2.5
229
+ */
230
+ $modified_contents .= substr( $contents, $update_button_end_pos );
231
+
232
+ $contents = $modified_contents;
233
+ }
234
+
235
+ echo $contents;
236
+ }
237
+
238
+ /**
239
+ * @author Vova Feldman (@svovaf)
240
+ * @since 2.0.0
241
+ */
242
+ private function add_transient_filters() {
243
+ if ( $this->_fs->is_premium() && ! $this->_fs->is_tracking_allowed() ) {
244
+ $this->_logger->log( 'Opted out sites cannot receive automatic software updates.' );
245
+
246
+ return;
247
+ }
248
+
249
+ add_filter( 'pre_set_site_transient_update_plugins', array(
250
+ &$this,
251
+ 'pre_set_site_transient_update_plugins_filter'
252
+ ) );
253
+
254
+ add_filter( 'pre_set_site_transient_update_themes', array(
255
+ &$this,
256
+ 'pre_set_site_transient_update_plugins_filter'
257
+ ) );
258
+ }
259
+
260
+ /**
261
+ * @author Vova Feldman (@svovaf)
262
+ * @since 2.0.0
263
+ */
264
+ private function remove_transient_filters() {
265
+ remove_filter( 'pre_set_site_transient_update_plugins', array(
266
+ &$this,
267
+ 'pre_set_site_transient_update_plugins_filter'
268
+ ) );
269
+
270
+ remove_filter( 'pre_set_site_transient_update_themes', array(
271
+ &$this,
272
+ 'pre_set_site_transient_update_plugins_filter'
273
+ ) );
274
+ }
275
+
276
+ /**
277
+ * Capture plugin update row by turning output buffering.
278
+ *
279
+ * @author Vova Feldman (@svovaf)
280
+ * @since 1.1.6
281
+ */
282
+ function catch_plugin_update_row() {
283
+ ob_start();
284
+ }
285
+
286
+ /**
287
+ * Overrides default update message format with "renew your license" message.
288
+ *
289
+ * @author Vova Feldman (@svovaf)
290
+ * @since 1.1.6
291
+ *
292
+ * @param string $file
293
+ * @param array $plugin_data
294
+ */
295
+ function edit_and_echo_plugin_update_row( $file, $plugin_data ) {
296
+ $plugin_update_row = ob_get_clean();
297
+
298
+ $current = get_site_transient( 'update_plugins' );
299
+ if ( ! isset( $current->response[ $file ] ) ) {
300
+ echo $plugin_update_row;
301
+
302
+ return;
303
+ }
304
+
305
+ $r = $current->response[ $file ];
306
+
307
+ $has_beta_update = $this->_fs->has_beta_update();
308
+
309
+ if ( $this->_fs->has_any_active_valid_license() ) {
310
+ if ( $has_beta_update ) {
311
+ /**
312
+ * Turn the "new version" text into "new Beta version".
313
+ *
314
+ * Sample input:
315
+ * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
316
+ * Output:
317
+ * There is a new Beta version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
318
+ *
319
+ * @author Leo Fajardo (@leorw)
320
+ * @since 2.3.0
321
+ */
322
+ $plugin_update_row = preg_replace(
323
+ '/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
324
+ (
325
+ '$1' .
326
+ sprintf(
327
+ fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
328
+ $has_beta_update ?
329
+ fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
330
+ fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ),
331
+ $this->_fs->get_plugin_title()
332
+ ) .
333
+ ' ' .
334
+ '$3' .
335
+ '$6'
336
+ ),
337
+ $plugin_update_row
338
+ );
339
+ }
340
+ } else {
341
+ /**
342
+ * Turn the "new version" text into a link that opens the plugin information dialog when clicked and
343
+ * make the "View version x details" text link to the checkout page instead of opening the plugin
344
+ * information dialog when clicked.
345
+ *
346
+ * Sample input:
347
+ * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
348
+ * Output:
349
+ * There is a <a href="...>new version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
350
+ * OR
351
+ * There is a <a href="...>new Beta version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
352
+ *
353
+ * @author Leo Fajardo (@leorw)
354
+ */
355
+ $plugin_update_row = preg_replace(
356
+ '/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
357
+ (
358
+ '$1' .
359
+ sprintf(
360
+ fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
361
+ sprintf(
362
+ '<a href="$4"%s>%s</a>',
363
+ '$5',
364
+ $has_beta_update ?
365
+ fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
366
+ fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() )
367
+ ),
368
+ $this->_fs->get_plugin_title()
369
+ ) .
370
+ ' ' .
371
+ $this->_fs->version_upgrade_checkout_link( $r->new_version ) .
372
+ '$6'
373
+ ),
374
+ $plugin_update_row
375
+ );
376
+ }
377
+
378
+ if (
379
+ $this->_fs->is_plugin() &&
380
+ isset( $r->upgrade_notice ) &&
381
+ strlen( trim( $r->upgrade_notice ) ) > 0
382
+ ) {
383
+ $slug = $this->_fs->get_slug();
384
+
385
+ $upgrade_notice_html = sprintf(
386
+ '<p class="notice fs-upgrade-notice fs-slug-%1$s fs-type-%2$s" data-slug="%1$s" data-type="%2$s"><strong>%3$s</strong> %4$s</p>',
387
+ $slug,
388
+ $this->_fs->get_module_type(),
389
+ fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ),
390
+ esc_html( $r->upgrade_notice )
391
+ );
392
+
393
+ $plugin_update_row = str_replace( '</div>', '</div>' . $upgrade_notice_html, $plugin_update_row );
394
+ }
395
+
396
+ echo $plugin_update_row;
397
+ }
398
+
399
+ /**
400
+ * @author Leo Fajardo (@leorw)
401
+ * @since 2.0.2
402
+ *
403
+ * @param array $prepared_themes
404
+ *
405
+ * @return array
406
+ */
407
+ function change_theme_update_info_html( $prepared_themes ) {
408
+ $theme_basename = $this->_fs->get_plugin_basename();
409
+
410
+ if ( ! isset( $prepared_themes[ $theme_basename ] ) ) {
411
+ return $prepared_themes;
412
+ }
413
+
414
+ $themes_update = get_site_transient( 'update_themes' );
415
+ if ( ! isset( $themes_update->response[ $theme_basename ] ) ||
416
+ empty( $themes_update->response[ $theme_basename ]['package'] )
417
+ ) {
418
+ return $prepared_themes;
419
+ }
420
+
421
+ $prepared_themes[ $theme_basename ]['update'] = preg_replace(
422
+ '/(\<p.+>)(.+)(\<a.+\<a.+)\.(.+\<\/p\>)/is',
423
+ '$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) .
424
+ '$4',
425
+ $prepared_themes[ $theme_basename ]['update']
426
+ );
427
+
428
+ // Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page.
429
+ $prepared_themes[ $theme_basename ]['hasPackage'] = false;
430
+
431
+ return $prepared_themes;
432
+ }
433
+
434
+ /**
435
+ * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip.
436
+ * During development mode we want to be able updating plugin versions via our localhost repository. This
437
+ * filter white-list all domains including "api.freemius".
438
+ *
439
+ * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/
440
+ *
441
+ * @author Vova Feldman (@svovaf)
442
+ * @since 1.0.4
443
+ *
444
+ * @param bool $allow
445
+ * @param string $host
446
+ * @param string $url
447
+ *
448
+ * @return bool
449
+ */
450
+ function http_request_host_is_external_filter( $allow, $host, $url ) {
451
+ return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow;
452
+ }
453
+
454
+ /**
455
+ * Check for Updates at the defined API endpoint and modify the update array.
456
+ *
457
+ * This function dives into the update api just when WordPress creates its update array,
458
+ * then adds a custom API call and injects the custom plugin data retrieved from the API.
459
+ * It is reassembled from parts of the native WordPress plugin update code.
460
+ * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
461
+ *
462
+ * @author Vova Feldman (@svovaf)
463
+ * @since 1.0.4
464
+ *
465
+ * @uses FS_Api
466
+ *
467
+ * @param object $transient_data Update array build by WordPress.
468
+ *
469
+ * @return object Modified update array with custom plugin data.
470
+ */
471
+ function pre_set_site_transient_update_plugins_filter( $transient_data ) {
472
+ $this->_logger->entrance();
473
+
474
+ /**
475
+ * "plugins" or "themes".
476
+ *
477
+ * @author Leo Fajardo (@leorw)
478
+ * @since 1.2.2
479
+ */
480
+ $module_type = $this->_fs->get_module_type() . 's';
481
+
482
+ /**
483
+ * Ensure that we don't mix plugins update info with themes update info.
484
+ *
485
+ * @author Leo Fajardo (@leorw)
486
+ * @since 1.2.2
487
+ */
488
+ if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) {
489
+ return $transient_data;
490
+ }
491
+
492
+ if ( empty( $transient_data ) ||
493
+ defined( 'WP_FS__UNINSTALL_MODE' )
494
+ ) {
495
+ return $transient_data;
496
+ }
497
+
498
+ global $wp_current_filter;
499
+
500
+ $current_plugin_version = $this->_fs->get_plugin_version();
501
+
502
+ if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) {
503
+ if (
504
+ is_null( $this->_update_details ) ||
505
+ ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version )
506
+ ) {
507
+ /**
508
+ * After an update, clear the stored update details and reparse the plugin's main file in order to get
509
+ * the updated version's information and prevent the previous update information from showing up on the
510
+ * updates page.
511
+ *
512
+ * @author Leo Fajardo (@leorw)
513
+ * @since 2.3.1
514
+ */
515
+ $this->_update_details = null;
516
+ $current_plugin_version = $this->_fs->get_plugin_version( true );
517
+ }
518
+ }
519
+
520
+ if ( ! isset( $this->_update_details ) ) {
521
+ // Get plugin's newest update.
522
+ $new_version = $this->_fs->get_update(
523
+ false,
524
+ fs_request_get_bool( 'force-check' ),
525
+ WP_FS__TIME_24_HOURS_IN_SEC / 24,
526
+ $current_plugin_version
527
+ );
528
+
529
+ $this->_update_details = false;
530
+
531
+ if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) {
532
+ $this->_logger->log( 'Found newer plugin version ' . $new_version->version );
533
+
534
+ /**
535
+ * Cache plugin details locally since set_site_transient( 'update_plugins' )
536
+ * called multiple times and the non wp.org plugins are filtered after the
537
+ * call to .org.
538
+ *
539
+ * @since 1.1.8.1
540
+ */
541
+ $this->_update_details = $this->get_update_details( $new_version );
542
+ }
543
+ }
544
+
545
+ // Alias.
546
+ $basename = $this->_fs->premium_plugin_basename();
547
+
548
+ if ( is_object( $this->_update_details ) ) {
549
+ if ( isset( $transient_data->no_update ) ) {
550
+ unset( $transient_data->no_update[ $basename ] );
551
+ }
552
+
553
+ if ( ! isset( $transient_data->response ) ) {
554
+ $transient_data->response = array();
555
+ }
556
+
557
+ // Add plugin to transient data.
558
+ $transient_data->response[ $basename ] = $this->_fs->is_plugin() ?
559
+ $this->_update_details :
560
+ (array) $this->_update_details;
561
+ } else {
562
+ if ( isset( $transient_data->response ) ) {
563
+ /**
564
+ * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version.
565
+ *
566
+ * @author Leo Fajardo (@leorw)
567
+ * @since 2.3.0
568
+ */
569
+ unset( $transient_data->response[ $basename ] );
570
+ }
571
+
572
+ if ( ! isset( $transient_data->no_update ) ) {
573
+ $transient_data->no_update = array();
574
+ }
575
+
576
+ /**
577
+ * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI.
578
+ *
579
+ * @since 2.4.1
580
+ * @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/
581
+ */
582
+ $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ?
583
+ (object) array(
584
+ 'id' => $basename,
585
+ 'slug' => $this->_fs->get_slug(),
586
+ 'plugin' => $basename,
587
+ 'new_version' => $this->_fs->get_plugin_version(),
588
+ 'url' => '',
589
+ 'package' => '',
590
+ 'icons' => array(),
591
+ 'banners' => array(),
592
+ 'banners_rtl' => array(),
593
+ 'tested' => '',
594
+ 'requires_php' => '',
595
+ 'compatibility' => new stdClass(),
596
+ ) :
597
+ array(
598
+ 'theme' => $basename,
599
+ 'new_version' => $this->_fs->get_plugin_version(),
600
+ 'url' => '',
601
+ 'package' => '',
602
+ 'requires' => '',
603
+ 'requires_php' => '',
604
+ );
605
+ }
606
+
607
+ $slug = $this->_fs->get_slug();
608
+
609
+ if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) {
610
+ if ( ! isset( $this->_translation_updates ) ) {
611
+ $this->_translation_updates = array();
612
+
613
+ if ( current_user_can( 'update_languages' ) ) {
614
+ $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
615
+ if ( ! empty( $translation_updates ) ) {
616
+ $this->_translation_updates = $translation_updates;
617
+ }
618
+ }
619
+ }
620
+
621
+ if ( ! empty( $this->_translation_updates ) ) {
622
+ $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ?
623
+ $transient_data->translations :
624
+ array();
625
+
626
+ $current_plugin_translation_updates_map = array();
627
+ foreach ( $all_translation_updates as $key => $translation_update ) {
628
+ if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) {
629
+ $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update;
630
+ unset( $all_translation_updates[ $key ] );
631
+ }
632
+ }
633
+
634
+ foreach ( $this->_translation_updates as $translation_update ) {
635
+ $lang = $translation_update['language'];
636
+ if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
637
+ version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' )
638
+ ) {
639
+ $current_plugin_translation_updates_map[ $lang ] = $translation_update;
640
+ }
641
+ }
642
+
643
+ $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) );
644
+ }
645
+ }
646
+
647
+ return $transient_data;
648
+ }
649
+
650
+ /**
651
+ * Get module's required data for the updates mechanism.
652
+ *
653
+ * @author Vova Feldman (@svovaf)
654
+ * @since 2.0.0
655
+ *
656
+ * @param \FS_Plugin_Tag $new_version
657
+ *
658
+ * @return object
659
+ */
660
+ function get_update_details( FS_Plugin_Tag $new_version ) {
661
+ $update = new stdClass();
662
+ $update->slug = $this->_fs->get_slug();
663
+ $update->new_version = $new_version->version;
664
+ $update->url = WP_FS__ADDRESS;
665
+ $update->package = $new_version->url;
666
+ $update->tested = $new_version->tested_up_to_version;
667
+ $update->requires = $new_version->requires_platform_version;
668
+
669
+ $icon = $this->_fs->get_local_icon_url();
670
+
671
+ if ( ! empty( $icon ) ) {
672
+ $update->icons = array(
673
+ // '1x' => $icon,
674
+ // '2x' => $icon,
675
+ 'default' => $icon,
676
+ );
677
+ }
678
+
679
+ if ( $this->_fs->is_premium() ) {
680
+ $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
681
+
682
+ if (
683
+ isset( $latest_tag->readme ) &&
684
+ isset( $latest_tag->readme->upgrade_notice ) &&
685
+ ! empty( $latest_tag->readme->upgrade_notice )
686
+ ) {
687
+ $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
688
+ }
689
+ }
690
+
691
+ $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
692
+
693
+ return $update;
694
+ }
695
+
696
+ /**
697
+ * @author Leo Fajardo (@leorw)
698
+ * @since 2.3.0
699
+ *
700
+ * @param FS_Plugin_Tag $new_version
701
+ *
702
+ * @return bool
703
+ */
704
+ private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
705
+ $query_str = parse_url( $new_version->url, PHP_URL_QUERY );
706
+ if ( empty( $query_str ) ) {
707
+ return false;
708
+ }
709
+
710
+ parse_str( $query_str, $params );
711
+
712
+ return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
713
+ }
714
+
715
+ /**
716
+ * Update the updates transient with the module's update information.
717
+ *
718
+ * This method is required for multisite environment.
719
+ * If a module is site activated (not network) and not on the main site,
720
+ * the module will NOT be executed on the network level, therefore, the
721
+ * custom updates logic will not be executed as well, so unless we force
722
+ * the injection of the update into the updates transient, premium updates
723
+ * will not work.
724
+ *
725
+ * @author Vova Feldman (@svovaf)
726
+ * @since 2.0.0
727
+ *
728
+ * @param \FS_Plugin_Tag $new_version
729
+ */
730
+ function set_update_data( FS_Plugin_Tag $new_version ) {
731
+ $this->_logger->entrance();
732
+
733
+ if ( ! $this->is_new_version_premium( $new_version ) ) {
734
+ return;
735
+ }
736
+
737
+ $transient_key = "update_{$this->_fs->get_module_type()}s";
738
+
739
+ $transient_data = get_site_transient( $transient_key );
740
+
741
+ $transient_data = is_object( $transient_data ) ?
742
+ $transient_data :
743
+ new stdClass();
744
+
745
+ // Alias.
746
+ $basename = $this->_fs->get_plugin_basename();
747
+ $is_plugin = $this->_fs->is_plugin();
748
+
749
+ if ( ! isset( $transient_data->response ) ||
750
+ ! is_array( $transient_data->response )
751
+ ) {
752
+ $transient_data->response = array();
753
+ } else if ( ! empty( $transient_data->response[ $basename ] ) ) {
754
+ $version = $is_plugin ?
755
+ ( ! empty( $transient_data->response[ $basename ]->new_version ) ?
756
+ $transient_data->response[ $basename ]->new_version :
757
+ null
758
+ ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ?
759
+ $transient_data->response[ $basename ]['new_version'] :
760
+ null
761
+ );
762
+
763
+ if ( $version == $new_version->version ) {
764
+ // The update data is already set.
765
+ return;
766
+ }
767
+ }
768
+
769
+ // Remove the added filters.
770
+ $this->remove_transient_filters();
771
+
772
+ $this->_update_details = $this->get_update_details( $new_version );
773
+
774
+ // Set update data in transient.
775
+ $transient_data->response[ $basename ] = $is_plugin ?
776
+ $this->_update_details :
777
+ (array) $this->_update_details;
778
+
779
+ if ( ! isset( $transient_data->checked ) ||
780
+ ! is_array( $transient_data->checked )
781
+ ) {
782
+ $transient_data->checked = array();
783
+ }
784
+
785
+ // Flag the module as if it was already checked.
786
+ $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version();
787
+ $transient_data->last_checked = time();
788
+
789
+ set_site_transient( $transient_key, $transient_data );
790
+
791
+ $this->add_transient_filters();
792
+ }
793
+
794
+ /**
795
+ * @author Leo Fajardo (@leorw)
796
+ * @since 2.0.2
797
+ */
798
+ function delete_update_data() {
799
+ $this->_logger->entrance();
800
+
801
+ $transient_key = "update_{$this->_fs->get_module_type()}s";
802
+
803
+ $transient_data = get_site_transient( $transient_key );
804
+
805
+ // Alias
806
+ $basename = $this->_fs->get_plugin_basename();
807
+
808
+ if ( ! is_object( $transient_data ) ||
809
+ ! isset( $transient_data->response ) ||
810
+ ! is_array( $transient_data->response ) ||
811
+ empty( $transient_data->response[ $basename ] )
812
+ ) {
813
+ return;
814
+ }
815
+
816
+ unset( $transient_data->response[ $basename ] );
817
+
818
+ // Remove the added filters.
819
+ $this->remove_transient_filters();
820
+
821
+ set_site_transient( $transient_key, $transient_data );
822
+
823
+ $this->add_transient_filters();
824
+ }
825
+
826
+ /**
827
+ * Try to fetch plugin's info from .org repository.
828
+ *
829
+ * @author Vova Feldman (@svovaf)
830
+ * @since 1.0.5
831
+ *
832
+ * @param string $action
833
+ * @param object $args
834
+ *
835
+ * @return bool|mixed
836
+ */
837
+ static function _fetch_plugin_info_from_repository( $action, $args ) {
838
+ $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
839
+ if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
840
+ $url = set_url_scheme( $url, 'https' );
841
+ }
842
+
843
+ $args = array(
844
+ 'timeout' => 15,
845
+ 'body' => array(
846
+ 'action' => $action,
847
+ 'request' => serialize( $args )
848
+ )
849
+ );
850
+
851
+ $request = wp_remote_post( $url, $args );
852
+
853
+ if ( is_wp_error( $request ) ) {
854
+ return false;
855
+ }
856
+
857
+ $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
858
+
859
+ if ( ! is_object( $res ) && ! is_array( $res ) ) {
860
+ return false;
861
+ }
862
+
863
+ return $res;
864
+ }
865
+
866
+ /**
867
+ * Fetches module translation updates from wordpress.org.
868
+ *
869
+ * @author Leo Fajardo (@leorw)
870
+ * @since 2.1.2
871
+ *
872
+ * @param string $module_type
873
+ * @param string $slug
874
+ *
875
+ * @return array|null
876
+ */
877
+ private function fetch_wp_org_module_translation_updates( $module_type, $slug ) {
878
+ $plugin_data = $this->_fs->get_plugin_data();
879
+
880
+ $locales = array_values( get_available_languages() );
881
+ $locales = apply_filters( "{$module_type}_update_check_locales", $locales );
882
+ $locales = array_unique( $locales );
883
+
884
+ $plugin_basename = $this->_fs->get_plugin_basename();
885
+ if ( 'themes' === $module_type ) {
886
+ $plugin_basename = $slug;
887
+ }
888
+
889
+ global $wp_version;
890
+
891
+ $request_args = array(
892
+ 'timeout' => 15,
893
+ 'body' => array(
894
+ "{$module_type}" => json_encode(
895
+ array(
896
+ "{$module_type}" => array(
897
+ $plugin_basename => array(
898
+ 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ),
899
+ 'Author' => $plugin_data['Author'],
900
+ )
901
+ )
902
+ )
903
+ ),
904
+ 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ),
905
+ 'locale' => json_encode( $locales )
906
+ ),
907
+ 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) )
908
+ );
909
+
910
+ $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/";
911
+ if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
912
+ $url = set_url_scheme( $url, 'https' );
913
+ }
914
+
915
+ $raw_response = Freemius::safe_remote_post(
916
+ $url,
917
+ $request_args,
918
+ WP_FS__TIME_24_HOURS_IN_SEC,
919
+ WP_FS__TIME_12_HOURS_IN_SEC,
920
+ false
921
+ );
922
+
923
+ if ( is_wp_error( $raw_response ) ) {
924
+ return null;
925
+ }
926
+
927
+ $response = json_decode( wp_remote_retrieve_body( $raw_response ), true );
928
+
929
+ if ( ! is_array( $response ) ) {
930
+ return null;
931
+ }
932
+
933
+ if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) {
934
+ return null;
935
+ }
936
+
937
+ return $response['translations'];
938
+ }
939
+
940
+ /**
941
+ * @author Leo Fajardo (@leorw)
942
+ * @since 2.1.2
943
+ *
944
+ * @param string $module_type
945
+ * @param string $slug
946
+ *
947
+ * @return array
948
+ */
949
+ private function get_installed_translations( $module_type, $slug ) {
950
+ if ( function_exists( 'wp_get_installed_translations' ) ) {
951
+ return wp_get_installed_translations( $module_type );
952
+ }
953
+
954
+ $dir = "/{$module_type}";
955
+
956
+ if ( ! is_dir( WP_LANG_DIR . $dir ) )
957
+ return array();
958
+
959
+ $files = scandir( WP_LANG_DIR . $dir );
960
+ if ( ! $files )
961
+ return array();
962
+
963
+ $language_data = array();
964
+
965
+ foreach ( $files as $file ) {
966
+ if ( 0 !== strpos( $file, $slug ) ) {
967
+ continue;
968
+ }
969
+
970
+ if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) {
971
+ continue;
972
+ }
973
+
974
+ if ( substr( $file, -3 ) !== '.po' ) {
975
+ continue;
976
+ }
977
+
978
+ if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) {
979
+ continue;
980
+ }
981
+
982
+ if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) {
983
+ continue;
984
+ }
985
+
986
+ list( , $textdomain, $language ) = $match;
987
+
988
+ if ( '' === $textdomain ) {
989
+ $textdomain = 'default';
990
+ }
991
+
992
+ $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" );
993
+ }
994
+
995
+ return $language_data;
996
+ }
997
+
998
+ /**
999
+ * Updates information on the "View version x.x details" page with custom data.
1000
+ *
1001
+ * @author Vova Feldman (@svovaf)
1002
+ * @since 1.0.4
1003
+ *
1004
+ * @uses FS_Api
1005
+ *
1006
+ * @param object $data
1007
+ * @param string $action
1008
+ * @param mixed $args
1009
+ *
1010
+ * @return object
1011
+ */
1012
+ function plugins_api_filter( $data, $action = '', $args = null ) {
1013
+ $this->_logger->entrance();
1014
+
1015
+ if ( ( 'plugin_information' !== $action ) ||
1016
+ ! isset( $args->slug )
1017
+ ) {
1018
+ return $data;
1019
+ }
1020
+
1021
+ $addon = false;
1022
+ $is_addon = false;
1023
+ $addon_version = false;
1024
+
1025
+ if ( $this->_fs->get_slug() !== $args->slug ) {
1026
+ $addon = $this->_fs->get_addon_by_slug( $args->slug );
1027
+
1028
+ if ( ! is_object( $addon ) ) {
1029
+ return $data;
1030
+ }
1031
+
1032
+ if ( $this->_fs->is_addon_activated( $addon->id ) ) {
1033
+ $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version();
1034
+ } else if ( $this->_fs->is_addon_installed( $addon->id ) ) {
1035
+ $addon_plugin_data = get_plugin_data(
1036
+ ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ),
1037
+ false,
1038
+ false
1039
+ );
1040
+
1041
+ if ( ! empty( $addon_plugin_data ) ) {
1042
+ $addon_version = $addon_plugin_data['Version'];
1043
+ }
1044
+ }
1045
+
1046
+ $is_addon = true;
1047
+ }
1048
+
1049
+ $plugin_in_repo = false;
1050
+ if ( ! $is_addon ) {
1051
+ // Try to fetch info from .org repository.
1052
+ $data = self::_fetch_plugin_info_from_repository( $action, $args );
1053
+
1054
+ $plugin_in_repo = ( false !== $data );
1055
+ }
1056
+
1057
+ if ( ! $plugin_in_repo ) {
1058
+ $data = $args;
1059
+
1060
+ // Fetch as much as possible info from local files.
1061
+ $plugin_local_data = $this->_fs->get_plugin_data();
1062
+ $data->name = $plugin_local_data['Name'];
1063
+ $data->author = $plugin_local_data['Author'];
1064
+ $data->sections = array(
1065
+ 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.',
1066
+ );
1067
+
1068
+ // @todo Store extra plugin info on Freemius or parse readme.txt markup.
1069
+ /*$info = $this->_fs->get_api_site_scope()->call('/information.json');
1070
+
1071
+ if ( !isset($info->error) ) {
1072
+ $data = $info;
1073
+ }*/
1074
+ }
1075
+
1076
+ $plugin_version = $is_addon ?
1077
+ $addon_version :
1078
+ $this->_fs->get_plugin_version();
1079
+
1080
+ // Get plugin's newest update.
1081
+ $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version );
1082
+
1083
+ if ( ! is_object( $new_version ) || empty( $new_version->version ) ) {
1084
+ $data->version = $plugin_version;
1085
+ } else {
1086
+ if ( $is_addon ) {
1087
+ $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1088
+ $data->slug = $addon->slug;
1089
+ $data->url = WP_FS__ADDRESS;
1090
+ $data->package = $new_version->url;
1091
+ }
1092
+
1093
+ if ( ! $plugin_in_repo ) {
1094
+ $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
1095
+ $data->requires = $new_version->requires_platform_version;
1096
+ $data->tested = $new_version->tested_up_to_version;
1097
+ }
1098
+
1099
+ $data->version = $new_version->version;
1100
+ $data->download_link = $new_version->url;
1101
+
1102
+ if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) {
1103
+ $new_version_readme_data = $new_version->readme;
1104
+ if ( isset( $new_version_readme_data->sections ) ) {
1105
+ $new_version_readme_data->sections = (array) $new_version_readme_data->sections;
1106
+ } else {
1107
+ $new_version_readme_data->sections = array();
1108
+ }
1109
+
1110
+ if ( isset( $data->sections ) ) {
1111
+ if ( isset( $data->sections['screenshots'] ) ) {
1112
+ $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots'];
1113
+ }
1114
+
1115
+ if ( isset( $data->sections['reviews'] ) ) {
1116
+ $new_version_readme_data->sections['reviews'] = $data->sections['reviews'];
1117
+ }
1118
+ }
1119
+
1120
+ if ( isset( $new_version_readme_data->banners ) ) {
1121
+ $new_version_readme_data->banners = (array) $new_version_readme_data->banners;
1122
+ } else if ( isset( $data->banners ) ) {
1123
+ $new_version_readme_data->banners = $data->banners;
1124
+ }
1125
+
1126
+ $wp_org_sections = array(
1127
+ 'author',
1128
+ 'author_profile',
1129
+ 'rating',
1130
+ 'ratings',
1131
+ 'num_ratings',
1132
+ 'support_threads',
1133
+ 'support_threads_resolved',
1134
+ 'active_installs',
1135
+ 'added',
1136
+ 'homepage'
1137
+ );
1138
+
1139
+ foreach ( $wp_org_sections as $wp_org_section ) {
1140
+ if ( isset( $data->{$wp_org_section} ) ) {
1141
+ $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section};
1142
+ }
1143
+ }
1144
+
1145
+ $data = $new_version_readme_data;
1146
+ }
1147
+ }
1148
+
1149
+ return $data;
1150
+ }
1151
+
1152
+ /**
1153
+ * @author Vova Feldman (@svovaf)
1154
+ * @since 1.2.1.7
1155
+ *
1156
+ * @param number|bool $addon_id
1157
+ * @param bool|string $newer_than Since 2.2.1
1158
+ * @param bool|string $fetch_readme Since 2.2.1
1159
+ *
1160
+ * @return object
1161
+ */
1162
+ private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1163
+ return $this->_fs->_fetch_latest_version( $addon_id, true, WP_FS__TIME_24_HOURS_IN_SEC, $newer_than, $fetch_readme );
1164
+ }
1165
+
1166
+ /**
1167
+ * Checks if a given basename has a matching folder name
1168
+ * with the current context plugin.
1169
+ *
1170
+ * @author Vova Feldman (@svovaf)
1171
+ * @since 1.2.1.6
1172
+ *
1173
+ * @return bool
1174
+ */
1175
+ private function is_correct_folder_name() {
1176
+ return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) );
1177
+ }
1178
+
1179
+ /**
1180
+ * This is a special after upgrade handler for migrating modules
1181
+ * that didn't use the '-premium' suffix folder structure before
1182
+ * the migration.
1183
+ *
1184
+ * @author Vova Feldman (@svovaf)
1185
+ * @since 1.2.1.6
1186
+ *
1187
+ * @param bool $response Install response.
1188
+ * @param array $hook_extra Extra arguments passed to hooked filters.
1189
+ * @param array $result Installation result data.
1190
+ *
1191
+ * @return bool
1192
+ */
1193
+ function _maybe_update_folder_name( $response, $hook_extra, $result ) {
1194
+ $basename = $this->_fs->get_plugin_basename();
1195
+
1196
+ if ( true !== $response ||
1197
+ empty( $hook_extra ) ||
1198
+ empty( $hook_extra['plugin'] ) ||
1199
+ $basename !== $hook_extra['plugin']
1200
+ ) {
1201
+ return $response;
1202
+ }
1203
+
1204
+ $active_plugins_basenames = get_option( 'active_plugins' );
1205
+
1206
+ foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) {
1207
+ if ( $basename === $active_plugin_basename ) {
1208
+ // Get filename including extension.
1209
+ $filename = basename( $basename );
1210
+
1211
+ $new_basename = plugin_basename(
1212
+ trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) .
1213
+ $filename
1214
+ );
1215
+
1216
+ // Verify that the expected correct path exists.
1217
+ if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) {
1218
+ // Override active plugin name.
1219
+ $active_plugins_basenames[ $key ] = $new_basename;
1220
+ update_option( 'active_plugins', $active_plugins_basenames );
1221
+ }
1222
+
1223
+ break;
1224
+ }
1225
+ }
1226
+
1227
+ return $response;
1228
+ }
1229
+
1230
+ #----------------------------------------------------------------------------------
1231
+ #region Auto Activation
1232
+ #----------------------------------------------------------------------------------
1233
+
1234
+ /**
1235
+ * Installs and active a plugin when explicitly requested that from a 3rd party service.
1236
+ *
1237
+ * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin.
1238
+ *
1239
+ * @link http://tgmpluginactivation.com/
1240
+ *
1241
+ * @author Vova Feldman
1242
+ * @since 1.2.1.7
1243
+ *
1244
+ * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
1245
+ *
1246
+ * @uses WP_Filesystem
1247
+ * @uses WP_Error
1248
+ * @uses WP_Upgrader
1249
+ * @uses Plugin_Upgrader
1250
+ * @uses Plugin_Installer_Skin
1251
+ * @uses Plugin_Upgrader_Skin
1252
+ *
1253
+ * @param number|bool $plugin_id
1254
+ *
1255
+ * @return array
1256
+ */
1257
+ function install_and_activate_plugin( $plugin_id = false ) {
1258
+ if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) {
1259
+ // Invalid plugin ID.
1260
+ return array(
1261
+ 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1262
+ 'code' => 'invalid_module_id',
1263
+ );
1264
+ }
1265
+
1266
+ $is_addon = false;
1267
+ if ( FS_Plugin::is_valid_id( $plugin_id ) &&
1268
+ $plugin_id != $this->_fs->get_id()
1269
+ ) {
1270
+ $addon = $this->_fs->get_addon( $plugin_id );
1271
+
1272
+ if ( ! is_object( $addon ) ) {
1273
+ // Invalid add-on ID.
1274
+ return array(
1275
+ 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1276
+ 'code' => 'invalid_module_id',
1277
+ );
1278
+ }
1279
+
1280
+ $slug = $addon->slug;
1281
+ $premium_slug = $addon->premium_slug;
1282
+ $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1283
+
1284
+ $is_addon = true;
1285
+ } else {
1286
+ $slug = $this->_fs->get_slug();
1287
+ $premium_slug = $this->_fs->get_premium_slug();
1288
+ $title = $this->_fs->get_plugin_title() .
1289
+ ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' );
1290
+ }
1291
+
1292
+ if ( $this->is_premium_plugin_active( $plugin_id ) ) {
1293
+ // Premium version already activated.
1294
+ return array(
1295
+ 'message' => $is_addon ?
1296
+ $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) :
1297
+ $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ),
1298
+ 'code' => 'premium_installed',
1299
+ );
1300
+ }
1301
+
1302
+ $latest_version = $this->get_latest_download_details( $plugin_id, false, false );
1303
+ $target_folder = $premium_slug;
1304
+
1305
+ // Prep variables for Plugin_Installer_Skin class.
1306
+ $extra = array();
1307
+ $extra['slug'] = $target_folder;
1308
+ $source = $latest_version->url;
1309
+ $api = null;
1310
+
1311
+ $install_url = add_query_arg(
1312
+ array(
1313
+ 'action' => 'install-plugin',
1314
+ 'plugin' => urlencode( $slug ),
1315
+ ),
1316
+ 'update.php'
1317
+ );
1318
+
1319
+ if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
1320
+ // Include required resources for the installation.
1321
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1322
+ }
1323
+
1324
+ $skin_args = array(
1325
+ 'type' => 'web',
1326
+ 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ),
1327
+ 'url' => esc_url_raw( $install_url ),
1328
+ 'nonce' => 'install-plugin_' . $slug,
1329
+ 'plugin' => '',
1330
+ 'api' => $api,
1331
+ 'extra' => $extra,
1332
+ );
1333
+
1334
+ // $skin = new Automatic_Upgrader_Skin( $skin_args );
1335
+ // $skin = new Plugin_Installer_Skin( $skin_args );
1336
+ $skin = new WP_Ajax_Upgrader_Skin( $skin_args );
1337
+
1338
+ // Create a new instance of Plugin_Upgrader.
1339
+ $upgrader = new Plugin_Upgrader( $skin );
1340
+
1341
+ // Perform the action and install the plugin from the $source urldecode().
1342
+ add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
1343
+
1344
+ $install_result = $upgrader->install( $source );
1345
+
1346
+ remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 );
1347
+
1348
+ if ( is_wp_error( $install_result ) ) {
1349
+ return array(
1350
+ 'message' => $install_result->get_error_message(),
1351
+ 'code' => $install_result->get_error_code(),
1352
+ );
1353
+ } elseif ( is_wp_error( $skin->result ) ) {
1354
+ return array(
1355
+ 'message' => $skin->result->get_error_message(),
1356
+ 'code' => $skin->result->get_error_code(),
1357
+ );
1358
+ } elseif ( $skin->get_errors()->get_error_code() ) {
1359
+ return array(
1360
+ 'message' => $skin->get_error_messages(),
1361
+ 'code' => 'unknown',
1362
+ );
1363
+ } elseif ( is_null( $install_result ) ) {
1364
+ global $wp_filesystem;
1365
+
1366
+ $error_code = 'unable_to_connect_to_filesystem';
1367
+ $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' );
1368
+
1369
+ // Pass through the error from WP_Filesystem if one was raised.
1370
+ if ( $wp_filesystem instanceof WP_Filesystem_Base &&
1371
+ is_wp_error( $wp_filesystem->errors ) &&
1372
+ $wp_filesystem->errors->get_error_code()
1373
+ ) {
1374
+ $error_message = $wp_filesystem->errors->get_error_message();
1375
+ }
1376
+
1377
+ return array(
1378
+ 'message' => $error_message,
1379
+ 'code' => $error_code,
1380
+ );
1381
+ }
1382
+
1383
+ // Grab the full path to the main plugin's file.
1384
+ $plugin_activate = $upgrader->plugin_info();
1385
+
1386
+ // Try to activate the plugin.
1387
+ $activation_result = $this->try_activate_plugin( $plugin_activate );
1388
+
1389
+ if ( is_wp_error( $activation_result ) ) {
1390
+ return array(
1391
+ 'message' => $activation_result->get_error_message(),
1392
+ 'code' => $activation_result->get_error_code(),
1393
+ );
1394
+ }
1395
+
1396
+ return $skin->get_upgrade_messages();
1397
+ }
1398
+
1399
+ /**
1400
+ * Tries to activate a plugin. If fails, returns the error.
1401
+ *
1402
+ * @author Vova Feldman
1403
+ * @since 1.2.1.7
1404
+ *
1405
+ * @param string $file_path Path within wp-plugins/ to main plugin file.
1406
+ * This determines the styling of the output messages.
1407
+ *
1408
+ * @return bool|WP_Error
1409
+ */
1410
+ protected function try_activate_plugin( $file_path ) {
1411
+ $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() );
1412
+
1413
+ return is_wp_error( $activate ) ?
1414
+ $activate :
1415
+ true;
1416
+ }
1417
+
1418
+ /**
1419
+ * Check if a premium module version is already active.
1420
+ *
1421
+ * @author Vova Feldman
1422
+ * @since 1.2.1.7
1423
+ *
1424
+ * @param number|bool $plugin_id
1425
+ *
1426
+ * @return bool
1427
+ */
1428
+ private function is_premium_plugin_active( $plugin_id = false ) {
1429
+ if ( $plugin_id != $this->_fs->get_id() ) {
1430
+ return $this->_fs->is_addon_activated( $plugin_id, true );
1431
+ }
1432
+
1433
+ return is_plugin_active( $this->_fs->premium_plugin_basename() );
1434
+ }
1435
+
1436
+ /**
1437
+ * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below.
1438
+ *
1439
+ * @author Leo Fajardo (@leorw)
1440
+ * @since 2.2.1
1441
+ *
1442
+ * @param bool|WP_Error $response Response.
1443
+ * @param array $hook_extra Extra arguments passed to hooked filters.
1444
+ *
1445
+ * @return bool|WP_Error
1446
+ */
1447
+ static function _store_basename_for_source_adjustment( $response, $hook_extra ) {
1448
+ if ( isset( $hook_extra['plugin'] ) ) {
1449
+ self::$_upgrade_basename = $hook_extra['plugin'];
1450
+ } else if ( isset( $hook_extra['theme'] ) ) {
1451
+ self::$_upgrade_basename = $hook_extra['theme'];
1452
+ } else {
1453
+ self::$_upgrade_basename = null;
1454
+ }
1455
+
1456
+ return $response;
1457
+ }
1458
+
1459
+ /**
1460
+ * Adjust the plugin directory name if necessary.
1461
+ * Assumes plugin has a folder (not a single file plugin).
1462
+ *
1463
+ * The final destination directory of a plugin is based on the subdirectory name found in the
1464
+ * (un)zipped source. In some cases this subdirectory name is not the same as the expected
1465
+ * slug and the plugin will not be recognized as installed. This is fixed by adjusting
1466
+ * the temporary unzipped source subdirectory name to the expected plugin slug.
1467
+ *
1468
+ * @author Vova Feldman
1469
+ * @since 1.2.1.7
1470
+ * @since 2.2.1 The method was converted to static since when the admin update bulk products via the Updates section, the logic applies the `upgrader_source_selection` filter for every product that is being updated.
1471
+ *
1472
+ * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/.
1473
+ * @param string $remote_source Path to upgrade/zip-file-name.tmp.
1474
+ * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin.
1475
+ *
1476
+ * @return string|WP_Error
1477
+ */
1478
+ static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) {
1479
+ if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) {
1480
+ return $source;
1481
+ }
1482
+
1483
+ $basename = self::$_upgrade_basename;
1484
+ $is_theme = false;
1485
+
1486
+ // Figure out what the slug is supposed to be.
1487
+ if ( isset( $upgrader->skin->options['extra'] ) ) {
1488
+ // Set by the auto-install logic.
1489
+ $desired_slug = $upgrader->skin->options['extra']['slug'];
1490
+ } else if ( ! empty( $basename ) ) {
1491
+ /**
1492
+ * If it doesn't end with ".php", it's a theme.
1493
+ *
1494
+ * @author Leo Fajardo (@leorw)
1495
+ * @since 2.2.1
1496
+ */
1497
+ $is_theme = ( ! fs_ends_with( $basename, '.php' ) );
1498
+
1499
+ $desired_slug = ( ! $is_theme ) ?
1500
+ dirname( $basename ) :
1501
+ // Theme slug
1502
+ $basename;
1503
+ } else {
1504
+ // Can't figure out the desired slug, stop the execution.
1505
+ return $source;
1506
+ }
1507
+
1508
+ if ( is_multisite() ) {
1509
+ /**
1510
+ * If we are running in a multisite environment and the product is not network activated,
1511
+ * the instance will not exist anyway. Therefore, try to update the source if necessary
1512
+ * regardless if the Freemius instance of the product exists or not.
1513
+ *
1514
+ * @author Vova Feldman
1515
+ */
1516
+ } else if ( ! empty( $basename ) ) {
1517
+ $fs = Freemius::get_instance_by_file(
1518
+ $basename,
1519
+ $is_theme ?
1520
+ WP_FS__MODULE_TYPE_THEME :
1521
+ WP_FS__MODULE_TYPE_PLUGIN
1522
+ );
1523
+
1524
+ if ( ! is_object( $fs ) ) {
1525
+ /**
1526
+ * If the Freemius instance does not exist on a non-multisite network environment, it means that:
1527
+ * 1. The product is not powered by Freemius; OR
1528
+ * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change.
1529
+ *
1530
+ * @author Leo Fajardo (@leorw)
1531
+ * @since 2.2.1
1532
+ */
1533
+ return $source;
1534
+ }
1535
+ }
1536
+
1537
+ $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) );
1538
+
1539
+ if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) {
1540
+ $from_path = untrailingslashit( $source );
1541
+ $to_path = trailingslashit( $remote_source ) . $desired_slug;
1542
+
1543
+ if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) {
1544
+ return trailingslashit( $to_path );
1545
+ }
1546
+
1547
+ return new WP_Error(
1548
+ 'rename_failed',
1549
+ fs_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ),
1550
+ array(
1551
+ 'found' => $subdir_name,
1552
+ 'expected' => $desired_slug
1553
+ )
1554
+ );
1555
+ }
1556
+
1557
+ return $source;
1558
+ }
1559
+
1560
+ #endregion
1561
+ }
freemius/includes/class-fs-storage.php CHANGED
@@ -1,532 +1,532 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.2.3
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * Class FS_Storage
15
- *
16
- * A wrapper class for handling network level and single site level storage.
17
- *
18
- * @property bool $is_network_activation
19
- * @property int $network_install_blog_id
20
- * @property object $sync_cron
21
- */
22
- class FS_Storage {
23
- /**
24
- * @var FS_Storage[]
25
- */
26
- private static $_instances = array();
27
- /**
28
- * @var FS_Key_Value_Storage Site level storage.
29
- */
30
- private $_storage;
31
-
32
- /**
33
- * @var FS_Key_Value_Storage Network level storage.
34
- */
35
- private $_network_storage;
36
-
37
- /**
38
- * @var string
39
- */
40
- private $_module_type;
41
-
42
- /**
43
- * @var string
44
- */
45
- private $_module_slug;
46
-
47
- /**
48
- * @var int The ID of the blog that is associated with the current site level options.
49
- */
50
- private $_blog_id = 0;
51
-
52
- /**
53
- * @var bool
54
- */
55
- private $_is_multisite;
56
-
57
- /**
58
- * @var bool
59
- */
60
- private $_is_network_active = false;
61
-
62
- /**
63
- * @var bool
64
- */
65
- private $_is_delegated_connection = false;
66
-
67
- /**
68
- * @var array {
69
- * @key string Option name.
70
- * @value int If 0 store on the network level. If 1, store on the network level only if module was network level activated. If 2, store on the network level only if network activated and NOT delegated the connection.
71
- * }
72
- */
73
- private static $_NETWORK_OPTIONS_MAP;
74
-
75
- /**
76
- * @author Leo Fajardo (@leorw)
77
- *
78
- * @param string $module_type
79
- * @param string $slug
80
- *
81
- * @return FS_Storage
82
- */
83
- static function instance( $module_type, $slug ) {
84
- $key = $module_type . ':' . $slug;
85
-
86
- if ( ! isset( self::$_instances[ $key ] ) ) {
87
- self::$_instances[ $key ] = new FS_Storage( $module_type, $slug );
88
- }
89
-
90
- return self::$_instances[ $key ];
91
- }
92
-
93
- /**
94
- * @author Leo Fajardo (@leorw)
95
- *
96
- * @param string $module_type
97
- * @param string $slug
98
- */
99
- private function __construct( $module_type, $slug ) {
100
- $this->_module_type = $module_type;
101
- $this->_module_slug = $slug;
102
- $this->_is_multisite = is_multisite();
103
-
104
- if ( $this->_is_multisite ) {
105
- $this->_blog_id = get_current_blog_id();
106
- $this->_network_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, true );
107
- }
108
-
109
- $this->_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, $this->_blog_id );
110
- }
111
-
112
- /**
113
- * Tells this storage wrapper class that the context plugin is network active. This flag will affect how values
114
- * are retrieved/stored from/into the storage.
115
- *
116
- * @author Leo Fajardo (@leorw)
117
- *
118
- * @param bool $is_network_active
119
- * @param bool $is_delegated_connection
120
- */
121
- function set_network_active( $is_network_active = true, $is_delegated_connection = false ) {
122
- $this->_is_network_active = $is_network_active;
123
- $this->_is_delegated_connection = $is_delegated_connection;
124
- }
125
-
126
- /**
127
- * Switch the context of the site level storage manager.
128
- *
129
- * @author Vova Feldman (@svovaf)
130
- * @since 2.0.0
131
- *
132
- * @param int $blog_id
133
- */
134
- function set_site_blog_context( $blog_id ) {
135
- $this->_storage = $this->get_site_storage( $blog_id );
136
- $this->_blog_id = $blog_id;
137
- }
138
-
139
- /**
140
- * @author Leo Fajardo (@leorw)
141
- *
142
- * @param string $key
143
- * @param mixed $value
144
- * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
145
- * @param bool $flush
146
- */
147
- function store( $key, $value, $network_level_or_blog_id = null, $flush = true ) {
148
- if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) {
149
- $this->_network_storage->store( $key, $value, $flush );
150
- } else {
151
- $storage = $this->get_site_storage( $network_level_or_blog_id );
152
- $storage->store( $key, $value, $flush );
153
- }
154
- }
155
-
156
- /**
157
- * @author Leo Fajardo (@leorw)
158
- *
159
- * @param bool $store
160
- * @param string[] $exceptions Set of keys to keep and not clear.
161
- * @param int|null|bool $network_level_or_blog_id
162
- */
163
- function clear_all( $store = true, $exceptions = array(), $network_level_or_blog_id = null ) {
164
- if ( ! $this->_is_multisite ||
165
- false === $network_level_or_blog_id ||
166
- is_null( $network_level_or_blog_id ) ||
167
- is_numeric( $network_level_or_blog_id )
168
- ) {
169
- $storage = $this->get_site_storage( $network_level_or_blog_id );
170
- $storage->clear_all( $store, $exceptions );
171
- }
172
-
173
- if ( $this->_is_multisite &&
174
- ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) )
175
- ) {
176
- $this->_network_storage->clear_all( $store, $exceptions );
177
- }
178
- }
179
-
180
- /**
181
- * @author Leo Fajardo (@leorw)
182
- *
183
- * @param string $key
184
- * @param bool $store
185
- * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
186
- */
187
- function remove( $key, $store = true, $network_level_or_blog_id = null ) {
188
- if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) {
189
- $this->_network_storage->remove( $key, $store );
190
- } else {
191
- $storage = $this->get_site_storage( $network_level_or_blog_id );
192
- $storage->remove( $key, $store );
193
- }
194
- }
195
-
196
- /**
197
- * @author Leo Fajardo (@leorw)
198
- *
199
- * @param string $key
200
- * @param mixed $default
201
- * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
202
- *
203
- * @return mixed
204
- */
205
- function get( $key, $default = false, $network_level_or_blog_id = null ) {
206
- if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) {
207
- return $this->_network_storage->get( $key, $default );
208
- } else {
209
- $storage = $this->get_site_storage( $network_level_or_blog_id );
210
-
211
- return $storage->get( $key, $default );
212
- }
213
- }
214
-
215
- /**
216
- * Multisite activated:
217
- * true: Save network storage.
218
- * int: Save site specific storage.
219
- * false|0: Save current site storage.
220
- * null: Save network and current site storage.
221
- * Site level activated:
222
- * Save site storage.
223
- *
224
- * @author Vova Feldman (@svovaf)
225
- * @since 2.0.0
226
- *
227
- * @param bool|int|null $network_level_or_blog_id
228
- */
229
- function save( $network_level_or_blog_id = null ) {
230
- if ( $this->_is_network_active &&
231
- ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) )
232
- ) {
233
- $this->_network_storage->save();
234
- }
235
-
236
- if ( ! $this->_is_network_active || true !== $network_level_or_blog_id ) {
237
- $storage = $this->get_site_storage( $network_level_or_blog_id );
238
- $storage->save();
239
- }
240
- }
241
-
242
- /**
243
- * @author Vova Feldman (@svovaf)
244
- * @since 2.0.0
245
- *
246
- * @return string
247
- */
248
- function get_module_slug() {
249
- return $this->_module_slug;
250
- }
251
-
252
- /**
253
- * @author Vova Feldman (@svovaf)
254
- * @since 2.0.0
255
- *
256
- * @return string
257
- */
258
- function get_module_type() {
259
- return $this->_module_type;
260
- }
261
-
262
- /**
263
- * Migration script to the new storage data structure that is network compatible.
264
- *
265
- * IMPORTANT:
266
- * This method should be executed only after it is determined if this is a network
267
- * level compatible product activation.
268
- *
269
- * @author Vova Feldman (@svovaf)
270
- * @since 2.0.0
271
- */
272
- function migrate_to_network() {
273
- if ( ! $this->_is_multisite ) {
274
- return;
275
- }
276
-
277
- $updated = false;
278
-
279
- if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) {
280
- self::load_network_options_map();
281
- }
282
-
283
- foreach ( self::$_NETWORK_OPTIONS_MAP as $option => $storage_level ) {
284
- if ( ! $this->is_multisite_option( $option ) ) {
285
- continue;
286
- }
287
-
288
- if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) {
289
- // Migrate option to the network storage.
290
- $this->_network_storage->store( $option, $this->_storage->{$option}, false );
291
-
292
- /**
293
- * Remove the option from site level storage.
294
- *
295
- * IMPORTANT:
296
- * The line below is intentionally commented since we want to preserve the option
297
- * on the site storage level for "downgrade compatibility". Basically, if the user
298
- * will downgrade to an older version of the plugin with the prev storage structure,
299
- * it will continue working.
300
- *
301
- * @todo After a few releases we can remove this.
302
- */
303
- // $this->_storage->remove($option, false);
304
-
305
- $updated = true;
306
- }
307
- }
308
-
309
- if ( ! $updated ) {
310
- return;
311
- }
312
-
313
- // Update network level storage.
314
- $this->_network_storage->save();
315
- // $this->_storage->save();
316
- }
317
-
318
- #--------------------------------------------------------------------------------
319
- #region Helper Methods
320
- #--------------------------------------------------------------------------------
321
-
322
- /**
323
- * We don't want to load the map right away since it's not even needed in a non-MS environment.
324
- *
325
- * Example:
326
- * array(
327
- * 'option1' => 0, // Means that the option should always be stored on the network level.
328
- * 'option2' => 1, // Means that the option should be stored on the network level only when the module was network level activated.
329
- * 'option2' => 2, // Means that the option should be stored on the network level only when the module was network level activated AND the connection was NOT delegated.
330
- * 'option3' => 3, // Means that the option should always be stored on the site level.
331
- * )
332
- *
333
- * @author Vova Feldman (@svovaf)
334
- * @since 2.0.0
335
- */
336
- private static function load_network_options_map() {
337
- self::$_NETWORK_OPTIONS_MAP = array(
338
- // Network level options.
339
- 'affiliate_application_data' => 0,
340
- 'beta_data' => 0,
341
- 'connectivity_test' => 0,
342
- 'handle_gdpr_admin_notice' => 0,
343
- 'has_trial_plan' => 0,
344
- 'install_sync_timestamp' => 0,
345
- 'install_sync_cron' => 0,
346
- 'is_anonymous_ms' => 0,
347
- 'is_network_activated' => 0,
348
- 'is_on' => 0,
349
- 'is_plugin_new_install' => 0,
350
- 'network_install_blog_id' => 0,
351
- 'pending_sites_info' => 0,
352
- 'plugin_last_version' => 0,
353
- 'plugin_main_file' => 0,
354
- 'plugin_version' => 0,
355
- 'sdk_downgrade_mode' => 0,
356
- 'sdk_last_version' => 0,
357
- 'sdk_upgrade_mode' => 0,
358
- 'sdk_version' => 0,
359
- 'sticky_optin_added_ms' => 0,
360
- 'subscriptions' => 0,
361
- 'sync_timestamp' => 0,
362
- 'sync_cron' => 0,
363
- 'was_plugin_loaded' => 0,
364
- 'network_user_id' => 0,
365
- 'plugin_upgrade_mode' => 0,
366
- 'plugin_downgrade_mode' => 0,
367
- 'is_network_connected' => 0,
368
- /**
369
- * Special flag that is used when a super-admin upgrades to the new version of the SDK that
370
- * supports network level integration, when the connection decision wasn't made for all of the
371
- * sites in the network.
372
- */
373
- 'is_network_activation' => 0,
374
- 'license_migration' => 0,
375
-
376
- // When network activated, then network level.
377
- 'install_timestamp' => 1,
378
- 'prev_is_premium' => 1,
379
- 'require_license_activation' => 1,
380
-
381
- // If not network activated OR delegated, then site level.
382
- 'activation_timestamp' => 2,
383
- 'expired_license_notice_shown' => 2,
384
- 'is_whitelabeled' => 2,
385
- 'last_license_key' => 2,
386
- 'last_license_user_id' => 2,
387
- 'prev_user_id' => 2,
388
- 'sticky_optin_added' => 2,
389
- 'uninstall_reason' => 2,
390
- 'is_pending_activation' => 2,
391
- 'pending_license_key' => 2,
392
- 'is_extensions_tracking_allowed' => 2,
393
-
394
- // Site level options.
395
- 'is_anonymous' => 3,
396
- );
397
- }
398
-
399
- /**
400
- * This method will and should only be executed when is_multisite() is true.
401
- *
402
- * @author Vova Feldman (@svovaf)
403
- * @since 2.0.0
404
- *
405
- * @param string $key
406
- *
407
- * @return bool|mixed
408
- */
409
- private function is_multisite_option( $key ) {
410
- if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) {
411
- self::load_network_options_map();
412
- }
413
-
414
- if ( ! isset( self::$_NETWORK_OPTIONS_MAP[ $key ] ) ) {
415
- // Option not found -> use site level storage.
416
- return false;
417
- }
418
-
419
- if ( 0 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) {
420
- // Option found and set to always use the network level storage on a multisite.
421
- return true;
422
- }
423
-
424
- if ( 3 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) {
425
- // Option found and set to always use the site level storage on a multisite.
426
- return false;
427
- }
428
-
429
- if ( ! $this->_is_network_active ) {
430
- return false;
431
- }
432
-
433
- if ( 1 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) {
434
- // Network activated.
435
- return true;
436
- }
437
-
438
- if ( 2 === self::$_NETWORK_OPTIONS_MAP[ $key ] && ! $this->_is_delegated_connection ) {
439
- // Network activated and not delegated.
440
- return true;
441
- }
442
-
443
- return false;
444
- }
445
-
446
- /**
447
- * @author Leo Fajardo
448
- *
449
- * @param string $key
450
- * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
451
- *
452
- * @return bool
453
- */
454
- private function should_use_network_storage( $key, $network_level_or_blog_id = null ) {
455
- if ( ! $this->_is_multisite ) {
456
- // Not a multisite environment.
457
- return false;
458
- }
459
-
460
- if ( is_numeric( $network_level_or_blog_id ) ) {
461
- // Explicitly asked to use a specified blog storage.
462
- return false;
463
- }
464
-
465
- if ( is_bool( $network_level_or_blog_id ) ) {
466
- // Explicitly specified whether should use the network or blog level storage.
467
- return $network_level_or_blog_id;
468
- }
469
-
470
- // Determine which storage to use based on the option.
471
- return $this->is_multisite_option( $key );
472
- }
473
-
474
- /**
475
- * @author Vova Feldman (@svovaf)
476
- * @since 2.0.0
477
- *
478
- * @param int $blog_id
479
- *
480
- * @return \FS_Key_Value_Storage
481
- */
482
- private function get_site_storage( $blog_id = 0 ) {
483
- if ( ! is_numeric( $blog_id ) ||
484
- $blog_id == $this->_blog_id ||
485
- 0 == $blog_id
486
- ) {
487
- return $this->_storage;
488
- }
489
-
490
- return FS_Key_Value_Storage::instance(
491
- $this->_module_type . '_data',
492
- $this->_storage->get_secondary_id(),
493
- $blog_id
494
- );
495
- }
496
-
497
- #endregion
498
-
499
- #--------------------------------------------------------------------------------
500
- #region Magic methods
501
- #--------------------------------------------------------------------------------
502
-
503
- function __set( $k, $v ) {
504
- if ( $this->should_use_network_storage( $k ) ) {
505
- $this->_network_storage->{$k} = $v;
506
- } else {
507
- $this->_storage->{$k} = $v;
508
- }
509
- }
510
-
511
- function __isset( $k ) {
512
- return $this->should_use_network_storage( $k ) ?
513
- isset( $this->_network_storage->{$k} ) :
514
- isset( $this->_storage->{$k} );
515
- }
516
-
517
- function __unset( $k ) {
518
- if ( $this->should_use_network_storage( $k ) ) {
519
- unset( $this->_network_storage->{$k} );
520
- } else {
521
- unset( $this->_storage->{$k} );
522
- }
523
- }
524
-
525
- function __get( $k ) {
526
- return $this->should_use_network_storage( $k ) ?
527
- $this->_network_storage->{$k} :
528
- $this->_storage->{$k};
529
- }
530
-
531
- #endregion
532
  }
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.2.3
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * Class FS_Storage
15
+ *
16
+ * A wrapper class for handling network level and single site level storage.
17
+ *
18
+ * @property bool $is_network_activation
19
+ * @property int $network_install_blog_id
20
+ * @property object $sync_cron
21
+ */
22
+ class FS_Storage {
23
+ /**
24
+ * @var FS_Storage[]
25
+ */
26
+ private static $_instances = array();
27
+ /**
28
+ * @var FS_Key_Value_Storage Site level storage.
29
+ */
30
+ private $_storage;
31
+
32
+ /**
33
+ * @var FS_Key_Value_Storage Network level storage.
34
+ */
35
+ private $_network_storage;
36
+
37
+ /**
38
+ * @var string
39
+ */
40
+ private $_module_type;
41
+
42
+ /**
43
+ * @var string
44
+ */
45
+ private $_module_slug;
46
+
47
+ /**
48
+ * @var int The ID of the blog that is associated with the current site level options.
49
+ */
50
+ private $_blog_id = 0;
51
+
52
+ /**
53
+ * @var bool
54
+ */
55
+ private $_is_multisite;
56
+
57
+ /**
58
+ * @var bool
59
+ */
60
+ private $_is_network_active = false;
61
+
62
+ /**
63
+ * @var bool
64
+ */
65
+ private $_is_delegated_connection = false;
66
+
67
+ /**
68
+ * @var array {
69
+ * @key string Option name.
70
+ * @value int If 0 store on the network level. If 1, store on the network level only if module was network level activated. If 2, store on the network level only if network activated and NOT delegated the connection.
71
+ * }
72
+ */
73
+ private static $_NETWORK_OPTIONS_MAP;
74
+
75
+ /**
76
+ * @author Leo Fajardo (@leorw)
77
+ *
78
+ * @param string $module_type
79
+ * @param string $slug
80
+ *
81
+ * @return FS_Storage
82
+ */
83
+ static function instance( $module_type, $slug ) {
84
+ $key = $module_type . ':' . $slug;
85
+
86
+ if ( ! isset( self::$_instances[ $key ] ) ) {
87
+ self::$_instances[ $key ] = new FS_Storage( $module_type, $slug );
88
+ }
89
+
90
+ return self::$_instances[ $key ];
91
+ }
92
+
93
+ /**
94
+ * @author Leo Fajardo (@leorw)
95
+ *
96
+ * @param string $module_type
97
+ * @param string $slug
98
+ */
99
+ private function __construct( $module_type, $slug ) {
100
+ $this->_module_type = $module_type;
101
+ $this->_module_slug = $slug;
102
+ $this->_is_multisite = is_multisite();
103
+
104
+ if ( $this->_is_multisite ) {
105
+ $this->_blog_id = get_current_blog_id();
106
+ $this->_network_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, true );
107
+ }
108
+
109
+ $this->_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, $this->_blog_id );
110
+ }
111
+
112
+ /**
113
+ * Tells this storage wrapper class that the context plugin is network active. This flag will affect how values
114
+ * are retrieved/stored from/into the storage.
115
+ *
116
+ * @author Leo Fajardo (@leorw)
117
+ *
118
+ * @param bool $is_network_active
119
+ * @param bool $is_delegated_connection
120
+ */
121
+ function set_network_active( $is_network_active = true, $is_delegated_connection = false ) {
122
+ $this->_is_network_active = $is_network_active;
123
+ $this->_is_delegated_connection = $is_delegated_connection;
124
+ }
125
+
126
+ /**
127
+ * Switch the context of the site level storage manager.
128
+ *
129
+ * @author Vova Feldman (@svovaf)
130
+ * @since 2.0.0
131
+ *
132
+ * @param int $blog_id
133
+ */
134
+ function set_site_blog_context( $blog_id ) {
135
+ $this->_storage = $this->get_site_storage( $blog_id );
136
+ $this->_blog_id = $blog_id;
137
+ }
138
+
139
+ /**
140
+ * @author Leo Fajardo (@leorw)
141
+ *
142
+ * @param string $key
143
+ * @param mixed $value
144
+ * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
145
+ * @param bool $flush
146
+ */
147
+ function store( $key, $value, $network_level_or_blog_id = null, $flush = true ) {
148
+ if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) {
149
+ $this->_network_storage->store( $key, $value, $flush );
150
+ } else {
151
+ $storage = $this->get_site_storage( $network_level_or_blog_id );
152
+ $storage->store( $key, $value, $flush );
153
+ }
154
+ }
155
+
156
+ /**
157
+ * @author Leo Fajardo (@leorw)
158
+ *
159
+ * @param bool $store
160
+ * @param string[] $exceptions Set of keys to keep and not clear.
161
+ * @param int|null|bool $network_level_or_blog_id
162
+ */
163
+ function clear_all( $store = true, $exceptions = array(), $network_level_or_blog_id = null ) {
164
+ if ( ! $this->_is_multisite ||
165
+ false === $network_level_or_blog_id ||
166
+ is_null( $network_level_or_blog_id ) ||
167
+ is_numeric( $network_level_or_blog_id )
168
+ ) {
169
+ $storage = $this->get_site_storage( $network_level_or_blog_id );
170
+ $storage->clear_all( $store, $exceptions );
171
+ }
172
+
173
+ if ( $this->_is_multisite &&
174
+ ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) )
175
+ ) {
176
+ $this->_network_storage->clear_all( $store, $exceptions );
177
+ }
178
+ }
179
+
180
+ /**
181
+ * @author Leo Fajardo (@leorw)
182
+ *
183
+ * @param string $key
184
+ * @param bool $store
185
+ * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
186
+ */
187
+ function remove( $key, $store = true, $network_level_or_blog_id = null ) {
188
+ if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) {
189
+ $this->_network_storage->remove( $key, $store );
190
+ } else {
191
+ $storage = $this->get_site_storage( $network_level_or_blog_id );
192
+ $storage->remove( $key, $store );
193
+ }
194
+ }
195
+
196
+ /**
197
+ * @author Leo Fajardo (@leorw)
198
+ *
199
+ * @param string $key
200
+ * @param mixed $default
201
+ * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
202
+ *
203
+ * @return mixed
204
+ */
205
+ function get( $key, $default = false, $network_level_or_blog_id = null ) {
206
+ if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) {
207
+ return $this->_network_storage->get( $key, $default );
208
+ } else {
209
+ $storage = $this->get_site_storage( $network_level_or_blog_id );
210
+
211
+ return $storage->get( $key, $default );
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Multisite activated:
217
+ * true: Save network storage.
218
+ * int: Save site specific storage.
219
+ * false|0: Save current site storage.
220
+ * null: Save network and current site storage.
221
+ * Site level activated:
222
+ * Save site storage.
223
+ *
224
+ * @author Vova Feldman (@svovaf)
225
+ * @since 2.0.0
226
+ *
227
+ * @param bool|int|null $network_level_or_blog_id
228
+ */
229
+ function save( $network_level_or_blog_id = null ) {
230
+ if ( $this->_is_network_active &&
231
+ ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) )
232
+ ) {
233
+ $this->_network_storage->save();
234
+ }
235
+
236
+ if ( ! $this->_is_network_active || true !== $network_level_or_blog_id ) {
237
+ $storage = $this->get_site_storage( $network_level_or_blog_id );
238
+ $storage->save();
239
+ }
240
+ }
241
+
242
+ /**
243
+ * @author Vova Feldman (@svovaf)
244
+ * @since 2.0.0
245
+ *
246
+ * @return string
247
+ */
248
+ function get_module_slug() {
249
+ return $this->_module_slug;
250
+ }
251
+
252
+ /**
253
+ * @author Vova Feldman (@svovaf)
254
+ * @since 2.0.0
255
+ *
256
+ * @return string
257
+ */
258
+ function get_module_type() {
259
+ return $this->_module_type;
260
+ }
261
+
262
+ /**
263
+ * Migration script to the new storage data structure that is network compatible.
264
+ *
265
+ * IMPORTANT:
266
+ * This method should be executed only after it is determined if this is a network
267
+ * level compatible product activation.
268
+ *
269
+ * @author Vova Feldman (@svovaf)
270
+ * @since 2.0.0
271
+ */
272
+ function migrate_to_network() {
273
+ if ( ! $this->_is_multisite ) {
274
+ return;
275
+ }
276
+
277
+ $updated = false;
278
+
279
+ if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) {
280
+ self::load_network_options_map();
281
+ }
282
+
283
+ foreach ( self::$_NETWORK_OPTIONS_MAP as $option => $storage_level ) {
284
+ if ( ! $this->is_multisite_option( $option ) ) {
285
+ continue;
286
+ }
287
+
288
+ if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) {
289
+ // Migrate option to the network storage.
290
+ $this->_network_storage->store( $option, $this->_storage->{$option}, false );
291
+
292
+ /**
293
+ * Remove the option from site level storage.
294
+ *
295
+ * IMPORTANT:
296
+ * The line below is intentionally commented since we want to preserve the option
297
+ * on the site storage level for "downgrade compatibility". Basically, if the user
298
+ * will downgrade to an older version of the plugin with the prev storage structure,
299
+ * it will continue working.
300
+ *
301
+ * @todo After a few releases we can remove this.
302
+ */
303
+ // $this->_storage->remove($option, false);
304
+
305
+ $updated = true;
306
+ }
307
+ }
308
+
309
+ if ( ! $updated ) {
310
+ return;
311
+ }
312
+
313
+ // Update network level storage.
314
+ $this->_network_storage->save();
315
+ // $this->_storage->save();
316
+ }
317
+
318
+ #--------------------------------------------------------------------------------
319
+ #region Helper Methods
320
+ #--------------------------------------------------------------------------------
321
+
322
+ /**
323
+ * We don't want to load the map right away since it's not even needed in a non-MS environment.
324
+ *
325
+ * Example:
326
+ * array(
327
+ * 'option1' => 0, // Means that the option should always be stored on the network level.
328
+ * 'option2' => 1, // Means that the option should be stored on the network level only when the module was network level activated.
329
+ * 'option2' => 2, // Means that the option should be stored on the network level only when the module was network level activated AND the connection was NOT delegated.
330
+ * 'option3' => 3, // Means that the option should always be stored on the site level.
331
+ * )
332
+ *
333
+ * @author Vova Feldman (@svovaf)
334
+ * @since 2.0.0
335
+ */
336
+ private static function load_network_options_map() {
337
+ self::$_NETWORK_OPTIONS_MAP = array(
338
+ // Network level options.
339
+ 'affiliate_application_data' => 0,
340
+ 'beta_data' => 0,
341
+ 'connectivity_test' => 0,
342
+ 'handle_gdpr_admin_notice' => 0,
343
+ 'has_trial_plan' => 0,
344
+ 'install_sync_timestamp' => 0,
345
+ 'install_sync_cron' => 0,
346
+ 'is_anonymous_ms' => 0,
347
+ 'is_network_activated' => 0,
348
+ 'is_on' => 0,
349
+ 'is_plugin_new_install' => 0,
350
+ 'network_install_blog_id' => 0,
351
+ 'pending_sites_info' => 0,
352
+ 'plugin_last_version' => 0,
353
+ 'plugin_main_file' => 0,
354
+ 'plugin_version' => 0,
355
+ 'sdk_downgrade_mode' => 0,
356
+ 'sdk_last_version' => 0,
357
+ 'sdk_upgrade_mode' => 0,
358
+ 'sdk_version' => 0,
359
+ 'sticky_optin_added_ms' => 0,
360
+ 'subscriptions' => 0,
361
+ 'sync_timestamp' => 0,
362
+ 'sync_cron' => 0,
363
+ 'was_plugin_loaded' => 0,
364
+ 'network_user_id' => 0,
365
+ 'plugin_upgrade_mode' => 0,
366
+ 'plugin_downgrade_mode' => 0,
367
+ 'is_network_connected' => 0,
368
+ /**
369
+ * Special flag that is used when a super-admin upgrades to the new version of the SDK that
370
+ * supports network level integration, when the connection decision wasn't made for all of the
371
+ * sites in the network.
372
+ */
373
+ 'is_network_activation' => 0,
374
+ 'license_migration' => 0,
375
+
376
+ // When network activated, then network level.
377
+ 'install_timestamp' => 1,
378
+ 'prev_is_premium' => 1,
379
+ 'require_license_activation' => 1,
380
+
381
+ // If not network activated OR delegated, then site level.
382
+ 'activation_timestamp' => 2,
383
+ 'expired_license_notice_shown' => 2,
384
+ 'is_whitelabeled' => 2,
385
+ 'last_license_key' => 2,
386
+ 'last_license_user_id' => 2,
387
+ 'prev_user_id' => 2,
388
+ 'sticky_optin_added' => 2,
389
+ 'uninstall_reason' => 2,
390
+ 'is_pending_activation' => 2,
391
+ 'pending_license_key' => 2,
392
+ 'is_extensions_tracking_allowed' => 2,
393
+
394
+ // Site level options.
395
+ 'is_anonymous' => 3,
396
+ );
397
+ }
398
+
399
+ /**
400
+ * This method will and should only be executed when is_multisite() is true.
401
+ *
402
+ * @author Vova Feldman (@svovaf)
403
+ * @since 2.0.0
404
+ *
405
+ * @param string $key
406
+ *
407
+ * @return bool|mixed
408
+ */
409
+ private function is_multisite_option( $key ) {
410
+ if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) {
411
+ self::load_network_options_map();
412
+ }
413
+
414
+ if ( ! isset( self::$_NETWORK_OPTIONS_MAP[ $key ] ) ) {
415
+ // Option not found -> use site level storage.
416
+ return false;
417
+ }
418
+
419
+ if ( 0 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) {
420
+ // Option found and set to always use the network level storage on a multisite.
421
+ return true;
422
+ }
423
+
424
+ if ( 3 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) {
425
+ // Option found and set to always use the site level storage on a multisite.
426
+ return false;
427
+ }
428
+
429
+ if ( ! $this->_is_network_active ) {
430
+ return false;
431
+ }
432
+
433
+ if ( 1 === self::$_NETWORK_OPTIONS_MAP[ $key ] ) {
434
+ // Network activated.
435
+ return true;
436
+ }
437
+
438
+ if ( 2 === self::$_NETWORK_OPTIONS_MAP[ $key ] && ! $this->_is_delegated_connection ) {
439
+ // Network activated and not delegated.
440
+ return true;
441
+ }
442
+
443
+ return false;
444
+ }
445
+
446
+ /**
447
+ * @author Leo Fajardo
448
+ *
449
+ * @param string $key
450
+ * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP).
451
+ *
452
+ * @return bool
453
+ */
454
+ private function should_use_network_storage( $key, $network_level_or_blog_id = null ) {
455
+ if ( ! $this->_is_multisite ) {
456
+ // Not a multisite environment.
457
+ return false;
458
+ }
459
+
460
+ if ( is_numeric( $network_level_or_blog_id ) ) {
461
+ // Explicitly asked to use a specified blog storage.
462
+ return false;
463
+ }
464
+
465
+ if ( is_bool( $network_level_or_blog_id ) ) {
466
+ // Explicitly specified whether should use the network or blog level storage.
467
+ return $network_level_or_blog_id;
468
+ }
469
+
470
+ // Determine which storage to use based on the option.
471
+ return $this->is_multisite_option( $key );
472
+ }
473
+
474
+ /**
475
+ * @author Vova Feldman (@svovaf)
476
+ * @since 2.0.0
477
+ *
478
+ * @param int $blog_id
479
+ *
480
+ * @return \FS_Key_Value_Storage
481
+ */
482
+ private function get_site_storage( $blog_id = 0 ) {
483
+ if ( ! is_numeric( $blog_id ) ||
484
+ $blog_id == $this->_blog_id ||
485
+ 0 == $blog_id
486
+ ) {
487
+ return $this->_storage;
488
+ }
489
+
490
+ return FS_Key_Value_Storage::instance(
491
+ $this->_module_type . '_data',
492
+ $this->_storage->get_secondary_id(),
493
+ $blog_id
494
+ );
495
+ }
496
+
497
+ #endregion
498
+
499
+ #--------------------------------------------------------------------------------
500
+ #region Magic methods
501
+ #--------------------------------------------------------------------------------
502
+
503
+ function __set( $k, $v ) {
504
+ if ( $this->should_use_network_storage( $k ) ) {
505
+ $this->_network_storage->{$k} = $v;
506
+ } else {
507
+ $this->_storage->{$k} = $v;
508
+ }
509
+ }
510
+
511
+ function __isset( $k ) {
512
+ return $this->should_use_network_storage( $k ) ?
513
+ isset( $this->_network_storage->{$k} ) :
514
+ isset( $this->_storage->{$k} );
515
+ }
516
+
517
+ function __unset( $k ) {
518
+ if ( $this->should_use_network_storage( $k ) ) {
519
+ unset( $this->_network_storage->{$k} );
520
+ } else {
521
+ unset( $this->_storage->{$k} );
522
+ }
523
+ }
524
+
525
+ function __get( $k ) {
526
+ return $this->should_use_network_storage( $k ) ?
527
+ $this->_network_storage->{$k} :
528
+ $this->_storage->{$k};
529
+ }
530
+
531
+ #endregion
532
  }
freemius/includes/fs-core-functions.php CHANGED
@@ -1,1416 +1,1416 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.0.3
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- if ( ! function_exists( 'fs_dummy' ) ) {
14
- function fs_dummy() {
15
- }
16
- }
17
-
18
- /* Url.
19
- --------------------------------------------------------------------------------------------*/
20
- if ( ! function_exists( 'fs_get_url_daily_cache_killer' ) ) {
21
- function fs_get_url_daily_cache_killer() {
22
- return date( '\YY\Mm\Dd' );
23
- }
24
- }
25
-
26
- /* Templates / Views.
27
- --------------------------------------------------------------------------------------------*/
28
- if ( ! function_exists( 'fs_get_template_path' ) ) {
29
- function fs_get_template_path( $path ) {
30
- return WP_FS__DIR_TEMPLATES . '/' . trim( $path, '/' );
31
- }
32
-
33
- function fs_include_template( $path, &$params = null ) {
34
- $VARS = &$params;
35
- include fs_get_template_path( $path );
36
- }
37
-
38
- function fs_include_once_template( $path, &$params = null ) {
39
- $VARS = &$params;
40
- include_once fs_get_template_path( $path );
41
- }
42
-
43
- function fs_require_template( $path, &$params = null ) {
44
- $VARS = &$params;
45
- require fs_get_template_path( $path );
46
- }
47
-
48
- function fs_require_once_template( $path, &$params = null ) {
49
- $VARS = &$params;
50
- require_once fs_get_template_path( $path );
51
- }
52
-
53
- function fs_get_template( $path, &$params = null ) {
54
- ob_start();
55
-
56
- $VARS = &$params;
57
- require fs_get_template_path( $path );
58
-
59
- return ob_get_clean();
60
- }
61
- }
62
-
63
- /* Scripts and styles including.
64
- --------------------------------------------------------------------------------------------*/
65
-
66
- if ( ! function_exists( 'fs_asset_url' ) ) {
67
- /**
68
- * Generates an absolute URL to the given path. This function ensures that the URL will be correct whether the asset
69
- * is inside a plugin's folder or a theme's folder.
70
- *
71
- * Examples:
72
- * 1. "themes" folder
73
- * Path: C:/xampp/htdocs/fswp/wp-content/themes/twentytwelve/freemius/assets/css/admin/common.css
74
- * URL: http://fswp:8080/wp-content/themes/twentytwelve/freemius/assets/css/admin/common.css
75
- *
76
- * 2. "plugins" folder
77
- * Path: C:/xampp/htdocs/fswp/wp-content/plugins/rating-widget-premium/freemius/assets/css/admin/common.css
78
- * URL: http://fswp:8080/wp-content/plugins/rating-widget-premium/freemius/assets/css/admin/common.css
79
- *
80
- * @author Leo Fajardo (@leorw)
81
- * @since 1.2.2
82
- *
83
- * @param string $asset_abs_path Asset's absolute path.
84
- *
85
- * @return string Asset's URL.
86
- */
87
- function fs_asset_url( $asset_abs_path ) {
88
- $wp_content_dir = fs_normalize_path( WP_CONTENT_DIR );
89
- $asset_abs_path = fs_normalize_path( $asset_abs_path );
90
-
91
- if ( 0 === strpos( $asset_abs_path, $wp_content_dir ) ) {
92
- // Handle both theme and plugin assets located in the standard directories.
93
- $asset_rel_path = str_replace( $wp_content_dir, '', $asset_abs_path );
94
- $asset_url = content_url( fs_normalize_path( $asset_rel_path ) );
95
- } else {
96
- $wp_plugins_dir = fs_normalize_path( WP_PLUGIN_DIR );
97
- if ( 0 === strpos( $asset_abs_path, $wp_plugins_dir ) ) {
98
- // Try to handle plugin assets that may be located in a non-standard plugins directory.
99
- $asset_rel_path = str_replace( $wp_plugins_dir, '', $asset_abs_path );
100
- $asset_url = plugins_url( fs_normalize_path( $asset_rel_path ) );
101
- } else {
102
- // Try to handle theme assets that may be located in a non-standard themes directory.
103
- $active_theme_stylesheet = get_stylesheet();
104
- $wp_themes_dir = fs_normalize_path( trailingslashit( get_theme_root( $active_theme_stylesheet ) ) );
105
- $asset_rel_path = str_replace( $wp_themes_dir, '', fs_normalize_path( $asset_abs_path ) );
106
- $asset_url = trailingslashit( get_theme_root_uri( $active_theme_stylesheet ) ) . fs_normalize_path( $asset_rel_path );
107
- }
108
- }
109
-
110
- return $asset_url;
111
- }
112
- }
113
-
114
- if ( ! function_exists( 'fs_enqueue_local_style' ) ) {
115
- function fs_enqueue_local_style( $handle, $path, $deps = array(), $ver = false, $media = 'all' ) {
116
- wp_enqueue_style( $handle, fs_asset_url( WP_FS__DIR_CSS . '/' . trim( $path, '/' ) ), $deps, $ver, $media );
117
- }
118
- }
119
-
120
- if ( ! function_exists( 'fs_enqueue_local_script' ) ) {
121
- function fs_enqueue_local_script( $handle, $path, $deps = array(), $ver = false, $in_footer = 'all' ) {
122
- wp_enqueue_script( $handle, fs_asset_url( WP_FS__DIR_JS . '/' . trim( $path, '/' ) ), $deps, $ver, $in_footer );
123
- }
124
- }
125
-
126
- if ( ! function_exists( 'fs_img_url' ) ) {
127
- function fs_img_url( $path, $img_dir = WP_FS__DIR_IMG ) {
128
- return ( fs_asset_url( $img_dir . '/' . trim( $path, '/' ) ) );
129
- }
130
- }
131
-
132
- #--------------------------------------------------------------------------------
133
- #region Request handlers.
134
- #--------------------------------------------------------------------------------
135
-
136
- if ( ! function_exists( 'fs_request_get' ) ) {
137
- /**
138
- * A helper method to fetch GET/POST user input with an optional default value when the input is not set.
139
- * @author Vova Feldman (@svovaf)
140
- *
141
- * @param string $key
142
- * @param mixed $def
143
- * @param string|bool $type Since 1.2.1.7 - when set to 'get' will look for the value passed via querystring, when
144
- * set to 'post' will look for the value passed via the POST request's body, otherwise,
145
- * will check if the parameter was passed in any of the two.
146
- *
147
- * @return mixed
148
- */
149
- function fs_request_get( $key, $def = false, $type = false ) {
150
- if ( is_string( $type ) ) {
151
- $type = strtolower( $type );
152
- }
153
-
154
- /**
155
- * Note to WordPress.org Reviewers:
156
- * This is a helper method to fetch GET/POST user input with an optional default value when the input is not set. The actual sanitization is done in the scope of the function's usage.
157
- */
158
- switch ( $type ) {
159
- case 'post':
160
- $value = isset( $_POST[ $key ] ) ? $_POST[ $key ] : $def;
161
- break;
162
- case 'get':
163
- $value = isset( $_GET[ $key ] ) ? $_GET[ $key ] : $def;
164
- break;
165
- default:
166
- $value = isset( $_REQUEST[ $key ] ) ? $_REQUEST[ $key ] : $def;
167
- break;
168
- }
169
-
170
- return $value;
171
- }
172
- }
173
-
174
- if ( ! function_exists( 'fs_request_has' ) ) {
175
- function fs_request_has( $key ) {
176
- return isset( $_REQUEST[ $key ] );
177
- }
178
- }
179
-
180
- if ( ! function_exists( 'fs_request_get_bool' ) ) {
181
- /**
182
- * A helper method to fetch GET/POST user boolean input with an optional default value when the input is not set.
183
- *
184
- * @author Vova Feldman (@svovaf)
185
- *
186
- * @param string $key
187
- * @param bool $def
188
- *
189
- * @return bool|mixed
190
- */
191
- function fs_request_get_bool( $key, $def = false ) {
192
- $val = fs_request_get( $key, null );
193
-
194
- if ( is_null( $val ) ) {
195
- return $def;
196
- }
197
-
198
- if ( is_bool( $val ) ) {
199
- return $val;
200
- } else if ( is_numeric( $val ) ) {
201
- if ( 1 == $val ) {
202
- return true;
203
- } else if ( 0 == $val ) {
204
- return false;
205
- }
206
- } else if ( is_string( $val ) ) {
207
- $val = strtolower( $val );
208
-
209
- if ( 'true' === $val ) {
210
- return true;
211
- } else if ( 'false' === $val ) {
212
- return false;
213
- }
214
- }
215
-
216
- return $def;
217
- }
218
- }
219
-
220
- if ( ! function_exists( 'fs_request_is_post' ) ) {
221
- function fs_request_is_post() {
222
- return ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) );
223
- }
224
- }
225
-
226
- if ( ! function_exists( 'fs_request_is_get' ) ) {
227
- function fs_request_is_get() {
228
- return ( 'get' === strtolower( $_SERVER['REQUEST_METHOD'] ) );
229
- }
230
- }
231
-
232
- if ( ! function_exists( 'fs_get_action' ) ) {
233
- function fs_get_action( $action_key = 'action' ) {
234
- if ( ! empty( $_REQUEST[ $action_key ] ) && is_string( $_REQUEST[ $action_key ] ) ) {
235
- return strtolower( $_REQUEST[ $action_key ] );
236
- }
237
-
238
- if ( 'action' == $action_key ) {
239
- $action_key = 'fs_action';
240
-
241
- if ( ! empty( $_REQUEST[ $action_key ] ) && is_string( $_REQUEST[ $action_key ] ) ) {
242
- return strtolower( $_REQUEST[ $action_key ] );
243
- }
244
- }
245
-
246
- return false;
247
- }
248
- }
249
-
250
- if ( ! function_exists( 'fs_request_is_action' ) ) {
251
- function fs_request_is_action( $action, $action_key = 'action' ) {
252
- return ( strtolower( $action ) === fs_get_action( $action_key ) );
253
- }
254
- }
255
-
256
- if ( ! function_exists( 'fs_request_is_action_secure' ) ) {
257
- /**
258
- * @author Vova Feldman (@svovaf)
259
- * @since 1.0.0
260
- *
261
- * @since 1.2.1.5 Allow nonce verification.
262
- *
263
- * @param string $action
264
- * @param string $action_key
265
- * @param string $nonce_key
266
- *
267
- * @return bool
268
- */
269
- function fs_request_is_action_secure(
270
- $action,
271
- $action_key = 'action',
272
- $nonce_key = 'nonce'
273
- ) {
274
- if ( strtolower( $action ) !== fs_get_action( $action_key ) ) {
275
- return false;
276
- }
277
-
278
- $nonce = ! empty( $_REQUEST[ $nonce_key ] ) ?
279
- $_REQUEST[ $nonce_key ] :
280
- '';
281
-
282
- if ( empty( $nonce ) ||
283
- ( false === wp_verify_nonce( $nonce, $action ) )
284
- ) {
285
- return false;
286
- }
287
-
288
- return true;
289
- }
290
- }
291
-
292
- #endregion
293
-
294
- if ( ! function_exists( 'fs_is_plugin_page' ) ) {
295
- function fs_is_plugin_page( $page_slug ) {
296
- return ( is_admin() && $page_slug === fs_request_get( 'page' ) );
297
- }
298
- }
299
-
300
- if ( ! function_exists( 'fs_get_raw_referer' ) ) {
301
- /**
302
- * Retrieves unvalidated referer from '_wp_http_referer' or HTTP referer.
303
- *
304
- * Do not use for redirects, use {@see wp_get_referer()} instead.
305
- *
306
- * @since 1.2.3
307
- *
308
- * @return string|false Referer URL on success, false on failure.
309
- */
310
- function fs_get_raw_referer() {
311
- if ( function_exists( 'wp_get_raw_referer' ) ) {
312
- return wp_get_raw_referer();
313
- }
314
- if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
315
- return wp_unslash( $_REQUEST['_wp_http_referer'] );
316
- } else if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) {
317
- return wp_unslash( $_SERVER['HTTP_REFERER'] );
318
- }
319
-
320
- return false;
321
- }
322
- }
323
-
324
- /* Core UI.
325
- --------------------------------------------------------------------------------------------*/
326
- if ( ! function_exists( 'fs_ui_action_button' ) ) {
327
- /**
328
- * @param number $module_id
329
- * @param string $page
330
- * @param string $action
331
- * @param string $title
332
- * @param string $button_class
333
- * @param array $params
334
- * @param bool $is_primary
335
- * @param bool $is_small
336
- * @param string|bool $icon_class Optional class for an icon (since 1.1.7).
337
- * @param string|bool $confirmation Optional confirmation message before submit (since 1.1.7).
338
- * @param string $method Since 1.1.7
339
- *
340
- * @uses fs_ui_get_action_button()
341
- */
342
- function fs_ui_action_button(
343
- $module_id,
344
- $page,
345
- $action,
346
- $title,
347
- $button_class = '',
348
- $params = array(),
349
- $is_primary = true,
350
- $is_small = false,
351
- $icon_class = false,
352
- $confirmation = false,
353
- $method = 'GET'
354
- ) {
355
- echo fs_ui_get_action_button(
356
- $module_id,
357
- $page,
358
- $action,
359
- $title,
360
- $button_class,
361
- $params,
362
- $is_primary,
363
- $is_small,
364
- $icon_class,
365
- $confirmation,
366
- $method
367
- );
368
- }
369
- }
370
-
371
- if ( ! function_exists( 'fs_ui_get_action_button' ) ) {
372
- /**
373
- * @author Vova Feldman (@svovaf)
374
- * @since 1.1.7
375
- *
376
- * @param number $module_id
377
- * @param string $page
378
- * @param string $action
379
- * @param string $title
380
- * @param string $button_class
381
- * @param array $params
382
- * @param bool $is_primary
383
- * @param bool $is_small
384
- * @param string|bool $icon_class Optional class for an icon.
385
- * @param string|bool $confirmation Optional confirmation message before submit.
386
- * @param string $method
387
- *
388
- * @return string
389
- */
390
- function fs_ui_get_action_button(
391
- $module_id,
392
- $page,
393
- $action,
394
- $title,
395
- $button_class = '',
396
- $params = array(),
397
- $is_primary = true,
398
- $is_small = false,
399
- $icon_class = false,
400
- $confirmation = false,
401
- $method = 'GET'
402
- ) {
403
- // Prepend icon (if set).
404
- $title = ( is_string( $icon_class ) ? '<i class="' . $icon_class . '"></i> ' : '' ) . $title;
405
-
406
- if ( is_string( $confirmation ) ) {
407
- return sprintf( '<form action="%s" method="%s"><input type="hidden" name="fs_action" value="%s">%s<a href="#" class="%s" onclick="if (confirm(\'%s\')) this.parentNode.submit(); return false;">%s</a></form>',
408
- freemius( $module_id )->_get_admin_page_url( $page, $params ),
409
- $method,
410
- $action,
411
- wp_nonce_field( $action, '_wpnonce', true, false ),
412
- 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ),
413
- $confirmation,
414
- $title
415
- );
416
- } else if ( 'GET' !== strtoupper( $method ) ) {
417
- return sprintf( '<form action="%s" method="%s"><input type="hidden" name="fs_action" value="%s">%s<a href="#" class="%s" onclick="this.parentNode.submit(); return false;">%s</a></form>',
418
- freemius( $module_id )->_get_admin_page_url( $page, $params ),
419
- $method,
420
- $action,
421
- wp_nonce_field( $action, '_wpnonce', true, false ),
422
- 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ),
423
- $title
424
- );
425
- } else {
426
- return sprintf( '<a href="%s" class="%s">%s</a></form>',
427
- wp_nonce_url( freemius( $module_id )->_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ),
428
- 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ),
429
- $title
430
- );
431
- }
432
- }
433
-
434
- function fs_ui_action_link( $module_id, $page, $action, $title, $params = array() ) {
435
- ?><a class=""
436
- href="<?php echo wp_nonce_url( freemius( $module_id )->_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ) ?>"><?php echo $title ?></a><?php
437
- }
438
- }
439
-
440
- if ( ! function_exists( 'fs_get_entity' ) ) {
441
- /**
442
- * @author Leo Fajardo (@leorw)
443
- * @since 2.3.1
444
- *
445
- * @param mixed $entity
446
- * @param string $class
447
- *
448
- * @return FS_Plugin|FS_User|FS_Site|FS_Plugin_License|FS_Plugin_Plan|FS_Plugin_Tag|FS_Subscription
449
- */
450
- function fs_get_entity( $entity, $class ) {
451
- if ( ! is_object( $entity ) || $entity instanceof $class ) {
452
- return $entity;
453
- }
454
-
455
- return new $class( $entity );
456
- }
457
- }
458
-
459
- if ( ! function_exists( 'fs_get_entities' ) ) {
460
- /**
461
- * @author Leo Fajardo (@leorw)
462
- * @since 2.3.1
463
- *
464
- * @param mixed $entities
465
- * @param string $class_name
466
- *
467
- * @return FS_Plugin[]|FS_User[]|FS_Site[]|FS_Plugin_License[]|FS_Plugin_Plan[]|FS_Plugin_Tag[]|FS_Subscription[]
468
- */
469
- function fs_get_entities( $entities, $class_name ) {
470
- if ( ! is_array( $entities ) || empty( $entities ) ) {
471
- return $entities;
472
- }
473
-
474
- // Get first element.
475
- $first_array_element = reset( $entities );
476
-
477
- if ( $first_array_element instanceof $class_name ) {
478
- /**
479
- * If the first element of the array is an instance of the context class, assume that all other
480
- * elements are instances of the class.
481
- */
482
- return $entities;
483
- }
484
-
485
- if (
486
- is_array( $first_array_element ) &&
487
- ! empty( $first_array_element )
488
- ) {
489
- $first_array_element = reset( $first_array_element );
490
-
491
- if ( $first_array_element instanceof $class_name ) {
492
- /**
493
- * If the first element of the `$entities` array is an array whose first element is an instance of the
494
- * context class, assume that all other objects are instances of the class.
495
- */
496
- return $entities;
497
- }
498
- }
499
-
500
- foreach ( $entities as $key => $entities_or_entity ) {
501
- if ( is_array( $entities_or_entity ) ) {
502
- $entities[ $key ] = fs_get_entities( $entities_or_entity, $class_name );
503
- } else {
504
- $entities[ $key ] = fs_get_entity( $entities_or_entity, $class_name );
505
- }
506
- }
507
-
508
- return $entities;
509
- }
510
- }
511
-
512
- if ( ! function_exists( 'fs_nonce_url' ) ) {
513
- /**
514
- * Retrieve URL with nonce added to URL query.
515
- *
516
- * Originally was using `wp_nonce_url()` but the new version
517
- * changed the return value to escaped URL, that's not the expected
518
- * behaviour.
519
- *
520
- * @author Vova Feldman (@svovaf)
521
- * @since ~1.1.3
522
- *
523
- * @param string $actionurl URL to add nonce action.
524
- * @param int|string $action Optional. Nonce action name. Default -1.
525
- * @param string $name Optional. Nonce name. Default '_wpnonce'.
526
- *
527
- * @return string Escaped URL with nonce action added.
528
- */
529
- function fs_nonce_url( $actionurl, $action = - 1, $name = '_wpnonce' ) {
530
- return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
531
- }
532
- }
533
-
534
- if ( ! function_exists( 'fs_starts_with' ) ) {
535
- /**
536
- * Check if string starts with.
537
- *
538
- * @author Vova Feldman (@svovaf)
539
- * @since 1.1.3
540
- *
541
- * @param string $haystack
542
- * @param string $needle
543
- *
544
- * @return bool
545
- */
546
- function fs_starts_with( $haystack, $needle ) {
547
- $length = strlen( $needle );
548
-
549
- return ( substr( $haystack, 0, $length ) === $needle );
550
- }
551
- }
552
-
553
- if ( ! function_exists( 'fs_ends_with' ) ) {
554
- /**
555
- * Check if string ends with.
556
- *
557
- * @author Vova Feldman (@svovaf)
558
- * @since 2.0.0
559
- *
560
- * @param string $haystack
561
- * @param string $needle
562
- *
563
- * @return bool
564
- */
565
- function fs_ends_with( $haystack, $needle ) {
566
- $length = strlen( $needle );
567
- $start = $length * - 1; // negative
568
-
569
- return ( substr( $haystack, $start ) === $needle );
570
- }
571
- }
572
-
573
- if ( ! function_exists( 'fs_strip_url_protocol' ) ) {
574
- function fs_strip_url_protocol( $url ) {
575
- if ( ! fs_starts_with( $url, 'http' ) ) {
576
- return $url;
577
- }
578
-
579
- $protocol_pos = strpos( $url, '://' );
580
-
581
- if ( $protocol_pos > 5 ) {
582
- return $url;
583
- }
584
-
585
- return substr( $url, $protocol_pos + 3 );
586
- }
587
- }
588
-
589
- #region Url Canonization ------------------------------------------------------------------
590
-
591
- if ( ! function_exists( 'fs_canonize_url' ) ) {
592
- /**
593
- * @author Vova Feldman (@svovaf)
594
- * @since 1.1.3
595
- *
596
- * @param string $url
597
- * @param bool $omit_host
598
- * @param array $ignore_params
599
- *
600
- * @return string
601
- */
602
- function fs_canonize_url( $url, $omit_host = false, $ignore_params = array() ) {
603
- $parsed_url = parse_url( strtolower( $url ) );
604
-
605
- // if ( ! isset( $parsed_url['host'] ) ) {
606
- // return $url;
607
- // }
608
-
609
- $canonical = ( ( $omit_host || ! isset( $parsed_url['host'] ) ) ? '' : $parsed_url['host'] ) . $parsed_url['path'];
610
-
611
- if ( isset( $parsed_url['query'] ) ) {
612
- parse_str( $parsed_url['query'], $queryString );
613
- $canonical .= '?' . fs_canonize_query_string( $queryString, $ignore_params );
614
- }
615
-
616
- return $canonical;
617
- }
618
- }
619
-
620
- if ( ! function_exists( 'fs_canonize_query_string' ) ) {
621
- /**
622
- * @author Vova Feldman (@svovaf)
623
- * @since 1.1.3
624
- *
625
- * @param array $params
626
- * @param array $ignore_params
627
- * @param bool $params_prefix
628
- *
629
- * @return string
630
- */
631
- function fs_canonize_query_string( array $params, array &$ignore_params, $params_prefix = false ) {
632
- if ( ! is_array( $params ) || 0 === count( $params ) ) {
633
- return '';
634
- }
635
-
636
- // Url encode both keys and values
637
- $keys = fs_urlencode_rfc3986( array_keys( $params ) );
638
- $values = fs_urlencode_rfc3986( array_values( $params ) );
639
- $params = array_combine( $keys, $values );
640
-
641
- // Parameters are sorted by name, using lexicographical byte value ordering.
642
- // Ref: Spec: 9.1.1 (1)
643
- uksort( $params, 'strcmp' );
644
-
645
- $pairs = array();
646
- foreach ( $params as $parameter => $value ) {
647
- $lower_param = strtolower( $parameter );
648
-
649
- // Skip ignore params.
650
- if ( in_array( $lower_param, $ignore_params ) ||
651
- ( false !== $params_prefix && fs_starts_with( $lower_param, $params_prefix ) )
652
- ) {
653
- continue;
654
- }
655
-
656
- if ( is_array( $value ) ) {
657
- // If two or more parameters share the same name, they are sorted by their value
658
- // Ref: Spec: 9.1.1 (1)
659
- natsort( $value );
660
- foreach ( $value as $duplicate_value ) {
661
- $pairs[] = $lower_param . '=' . $duplicate_value;
662
- }
663
- } else {
664
- $pairs[] = $lower_param . '=' . $value;
665
- }
666
- }
667
-
668
- if ( 0 === count( $pairs ) ) {
669
- return '';
670
- }
671
-
672
- return implode( "&", $pairs );
673
- }
674
- }
675
-
676
- if ( ! function_exists( 'fs_urlencode_rfc3986' ) ) {
677
- /**
678
- * @author Vova Feldman (@svovaf)
679
- * @since 1.1.3
680
- *
681
- * @param string|string[] $input
682
- *
683
- * @return array|mixed|string
684
- */
685
- function fs_urlencode_rfc3986( $input ) {
686
- if ( is_array( $input ) ) {
687
- return array_map( 'fs_urlencode_rfc3986', $input );
688
- } else if ( is_scalar( $input ) ) {
689
- return str_replace( '+', ' ', str_replace( '%7E', '~', rawurlencode( $input ) ) );
690
- }
691
-
692
- return '';
693
- }
694
- }
695
-
696
- #endregion Url Canonization ------------------------------------------------------------------
697
-
698
- if ( ! function_exists( 'fs_download_image' ) ) {
699
- /**
700
- * @author Vova Feldman (@svovaf)
701
- *
702
- * @since 1.2.2 Changed to usage of WP_Filesystem_Direct.
703
- *
704
- * @param string $from URL
705
- * @param string $to File path.
706
- *
707
- * @return bool Is successfully downloaded.
708
- */
709
- function fs_download_image( $from, $to ) {
710
- $dir = dirname( $to );
711
-
712
- if ( 'direct' !== get_filesystem_method( array(), $dir ) ) {
713
- return false;
714
- }
715
-
716
- if ( ! class_exists( 'WP_Filesystem_Direct' ) ) {
717
- require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
718
- require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
719
- }
720
-
721
- $fs = new WP_Filesystem_Direct( '' );
722
- $tmpfile = download_url( $from );
723
-
724
- if ( $tmpfile instanceof WP_Error ) {
725
- // Issue downloading the file.
726
- return false;
727
- }
728
-
729
- $fs->copy( $tmpfile, $to );
730
- $fs->delete( $tmpfile );
731
-
732
- return true;
733
- }
734
- }
735
-
736
- /* General Utilities
737
- --------------------------------------------------------------------------------------------*/
738
-
739
- if ( ! function_exists( 'fs_sort_by_priority' ) ) {
740
- /**
741
- * Sorts an array by the value of the priority key.
742
- *
743
- * @author Daniel Iser (@danieliser)
744
- * @since 1.1.7
745
- *
746
- * @param $a
747
- * @param $b
748
- *
749
- * @return int
750
- */
751
- function fs_sort_by_priority( $a, $b ) {
752
-
753
- // If b has a priority and a does not, b wins.
754
- if ( ! isset( $a['priority'] ) && isset( $b['priority'] ) ) {
755
- return 1;
756
- } // If b has a priority and a does not, b wins.
757
- elseif ( isset( $a['priority'] ) && ! isset( $b['priority'] ) ) {
758
- return - 1;
759
- } // If neither has a priority or both priorities are equal its a tie.
760
- elseif ( ( ! isset( $a['priority'] ) && ! isset( $b['priority'] ) ) || $a['priority'] === $b['priority'] ) {
761
- return 0;
762
- }
763
-
764
- // If both have priority return the winner.
765
- return ( $a['priority'] < $b['priority'] ) ? - 1 : 1;
766
- }
767
- }
768
-
769
- #--------------------------------------------------------------------------------
770
- #region Localization
771
- #--------------------------------------------------------------------------------
772
-
773
- if ( ! function_exists( 'fs_text' ) ) {
774
- /**
775
- * Retrieve a translated text by key.
776
- *
777
- * @author Vova Feldman (@svovaf)
778
- * @since 1.2.1.7
779
- *
780
- * @param string $key
781
- * @param string $slug
782
- *
783
- * @return string
784
- *
785
- * @global $fs_text , $fs_text_overrides
786
- */
787
- function fs_text( $key, $slug = 'freemius' ) {
788
- global $fs_text,
789
- $fs_module_info_text,
790
- $fs_text_overrides;
791
-
792
- if ( isset( $fs_text_overrides[ $slug ] ) ) {
793
- if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) {
794
- return $fs_text_overrides[ $slug ][ $key ];
795
- }
796
-
797
- $lower_key = strtolower( $key );
798
- if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) {
799
- return $fs_text_overrides[ $slug ][ $lower_key ];
800
- }
801
- }
802
-
803
- if ( ! isset( $fs_text ) ) {
804
- $dir = defined( 'WP_FS__DIR_INCLUDES' ) ?
805
- WP_FS__DIR_INCLUDES :
806
- dirname( __FILE__ );
807
-
808
- require_once $dir . '/i18n.php';
809
- }
810
-
811
- if ( isset( $fs_text[ $key ] ) ) {
812
- return $fs_text[ $key ];
813
- }
814
-
815
- if ( isset( $fs_module_info_text[ $key ] ) ) {
816
- return $fs_module_info_text[ $key ];
817
- }
818
-
819
- return $key;
820
- }
821
-
822
- #region Private
823
-
824
- /**
825
- * Retrieve an inline translated text by key with a context.
826
- *
827
- * @author Vova Feldman (@svovaf)
828
- * @since 1.2.3
829
- *
830
- * @param string $text Translatable string.
831
- * @param string $context Context information for the translators.
832
- * @param string $key String key for overrides.
833
- * @param string $slug Module slug for overrides.
834
- *
835
- * @return string
836
- *
837
- * @global $fs_text_overrides
838
- */
839
- function _fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
840
- list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug );
841
-
842
- // Avoid misleading Theme Check warning.
843
- $fn = 'translate_with_gettext_context';
844
-
845
- return $fn( $text, $context, $text_domain );
846
- }
847
-
848
- #endregion
849
-
850
- /**
851
- * Retrieve an inline translated text by key with a context.
852
- *
853
- * @author Vova Feldman (@svovaf)
854
- * @since 1.2.3
855
- *
856
- * @param string $text Translatable string.
857
- * @param string $context Context information for the translators.
858
- * @param string $key String key for overrides.
859
- * @param string $slug Module slug for overrides.
860
- *
861
- * @return string
862
- *
863
- * @global $fs_text_overrides
864
- */
865
- function fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
866
- return _fs_text_x_inline( $text, $context, $key, $slug );
867
- }
868
-
869
- /**
870
- * Output a translated text by key.
871
- *
872
- * @author Vova Feldman (@svovaf)
873
- * @since 1.2.1.7
874
- *
875
- * @param string $key
876
- * @param string $slug
877
- */
878
- function fs_echo( $key, $slug = 'freemius' ) {
879
- echo fs_text( $key, $slug );
880
- }
881
-
882
- /**
883
- * Output an inline translated text.
884
- *
885
- * @author Vova Feldman (@svovaf)
886
- * @since 1.2.3
887
- *
888
- * @param string $text Translatable string.
889
- * @param string $key String key for overrides.
890
- * @param string $slug Module slug for overrides.
891
- */
892
- function fs_echo_inline( $text, $key = '', $slug = 'freemius' ) {
893
- echo _fs_text_inline( $text, $key, $slug );
894
- }
895
-
896
- /**
897
- * Output an inline translated text with a context.
898
- *
899
- * @author Vova Feldman (@svovaf)
900
- * @since 1.2.3
901
- *
902
- * @param string $text Translatable string.
903
- * @param string $context Context information for the translators.
904
- * @param string $key String key for overrides.
905
- * @param string $slug Module slug for overrides.
906
- */
907
- function fs_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
908
- echo _fs_text_x_inline( $text, $context, $key, $slug );
909
- }
910
- }
911
-
912
- if ( ! function_exists( 'fs_text_override' ) ) {
913
- /**
914
- * Get a translatable text override if exists, or `false`.
915
- *
916
- * @author Vova Feldman (@svovaf)
917
- * @since 1.2.1.7
918
- *
919
- * @param string $text Translatable string.
920
- * @param string $key String key for overrides.
921
- * @param string $slug Module slug for overrides.
922
- *
923
- * @return string|false
924
- */
925
- function fs_text_override( $text, $key, $slug ) {
926
- global $fs_text_overrides;
927
-
928
- /**
929
- * Check if string is overridden.
930
- */
931
- if ( ! isset( $fs_text_overrides[ $slug ] ) ) {
932
- return false;
933
- }
934
-
935
- if ( empty( $key ) ) {
936
- $key = strtolower( str_replace( ' ', '-', $text ) );
937
- }
938
-
939
- if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) {
940
- return $fs_text_overrides[ $slug ][ $key ];
941
- }
942
-
943
- $lower_key = strtolower( $key );
944
- if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) {
945
- return $fs_text_overrides[ $slug ][ $lower_key ];
946
- }
947
-
948
- return false;
949
- }
950
- }
951
-
952
- if ( ! function_exists( 'fs_text_and_domain' ) ) {
953
- /**
954
- * Get a translatable text and its text domain.
955
- *
956
- * When the text is overridden by the module, returns the overridden text and the text domain of the module. Otherwise, returns the original text and 'freemius' as the text domain.
957
- *
958
- * @author Vova Feldman (@svovaf)
959
- * @since 1.2.1.7
960
- *
961
- * @param string $text Translatable string.
962
- * @param string $key String key for overrides.
963
- * @param string $slug Module slug for overrides.
964
- *
965
- * @return string[]
966
- */
967
- function fs_text_and_domain( $text, $key, $slug ) {
968
- $override = fs_text_override( $text, $key, $slug );
969
-
970
- if ( false === $override ) {
971
- // No override, use FS text domain.
972
- $text_domain = 'freemius';
973
- } else {
974
- // Found an override.
975
- $text = $override;
976
- // Use the module's text domain.
977
- $text_domain = $slug;
978
- }
979
-
980
- return array( $text, $text_domain );
981
- }
982
- }
983
-
984
- if ( ! function_exists( '_fs_text_inline' ) ) {
985
- /**
986
- * Retrieve an inline translated text by key.
987
- *
988
- * @author Vova Feldman (@svovaf)
989
- * @since 1.2.3
990
- *
991
- * @param string $text Translatable string.
992
- * @param string $key String key for overrides.
993
- * @param string $slug Module slug for overrides.
994
- *
995
- * @return string
996
- *
997
- * @global $fs_text_overrides
998
- */
999
- function _fs_text_inline( $text, $key = '', $slug = 'freemius' ) {
1000
- list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug );
1001
-
1002
- // Avoid misleading Theme Check warning.
1003
- $fn = 'translate';
1004
-
1005
- return $fn( $text, $text_domain );
1006
- }
1007
- }
1008
-
1009
- if ( ! function_exists( 'fs_text_inline' ) ) {
1010
- /**
1011
- * Retrieve an inline translated text by key.
1012
- *
1013
- * @author Vova Feldman (@svovaf)
1014
- * @since 1.2.3
1015
- *
1016
- * @param string $text Translatable string.
1017
- * @param string $key String key for overrides.
1018
- * @param string $slug Module slug for overrides.
1019
- *
1020
- * @return string
1021
- *
1022
- * @global $fs_text_overrides
1023
- */
1024
- function fs_text_inline( $text, $key = '', $slug = 'freemius' ) {
1025
- return _fs_text_inline( $text, $key, $slug );
1026
- }
1027
- }
1028
-
1029
- if ( ! function_exists( 'fs_esc_attr' ) ) {
1030
- /**
1031
- * @author Vova Feldman
1032
- * @since 1.2.1.6
1033
- *
1034
- * @param string $key
1035
- * @param string $slug
1036
- *
1037
- * @return string
1038
- */
1039
- function fs_esc_attr( $key, $slug ) {
1040
- return esc_attr( fs_text( $key, $slug ) );
1041
- }
1042
- }
1043
-
1044
- if ( ! function_exists( 'fs_esc_attr_inline' ) ) {
1045
- /**
1046
- * @author Vova Feldman (@svovaf)
1047
- * @since 1.2.3
1048
- *
1049
- * @param string $text Translatable string.
1050
- * @param string $key String key for overrides.
1051
- * @param string $slug Module slug for overrides.
1052
- *
1053
- * @return string
1054
- */
1055
- function fs_esc_attr_inline( $text, $key = '', $slug = 'freemius' ) {
1056
- return esc_attr( _fs_text_inline( $text, $key, $slug ) );
1057
- }
1058
- }
1059
-
1060
- if ( ! function_exists( 'fs_esc_attr_x_inline' ) ) {
1061
- /**
1062
- * @author Vova Feldman (@svovaf)
1063
- * @since 1.2.3
1064
- *
1065
- * @param string $text Translatable string.
1066
- * @param string $context Context information for the translators.
1067
- * @param string $key String key for overrides.
1068
- * @param string $slug Module slug for overrides.
1069
- *
1070
- * @return string
1071
- */
1072
- function fs_esc_attr_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1073
- return esc_attr( _fs_text_x_inline( $text, $context, $key, $slug ) );
1074
- }
1075
- }
1076
-
1077
- if ( ! function_exists( 'fs_esc_attr_echo' ) ) {
1078
- /**
1079
- * @author Vova Feldman
1080
- * @since 1.2.1.6
1081
- *
1082
- * @param string $key
1083
- * @param string $slug
1084
- */
1085
- function fs_esc_attr_echo( $key, $slug ) {
1086
- echo esc_attr( fs_text( $key, $slug ) );
1087
- }
1088
- }
1089
-
1090
- if ( ! function_exists( 'fs_esc_attr_echo_inline' ) ) {
1091
- /**
1092
- * @author Vova Feldman (@svovaf)
1093
- * @since 1.2.3
1094
- *
1095
- * @param string $text Translatable string.
1096
- * @param string $key String key for overrides.
1097
- * @param string $slug Module slug for overrides.
1098
- */
1099
- function fs_esc_attr_echo_inline( $text, $key = '', $slug = 'freemius' ) {
1100
- echo esc_attr( _fs_text_inline( $text, $key, $slug ) );
1101
- }
1102
- }
1103
-
1104
- if ( ! function_exists( 'fs_esc_js' ) ) {
1105
- /**
1106
- * @author Vova Feldman
1107
- * @since 1.2.1.6
1108
- *
1109
- * @param string $key
1110
- * @param string $slug
1111
- *
1112
- * @return string
1113
- */
1114
- function fs_esc_js( $key, $slug ) {
1115
- return esc_js( fs_text( $key, $slug ) );
1116
- }
1117
- }
1118
-
1119
- if ( ! function_exists( 'fs_esc_js_inline' ) ) {
1120
- /**
1121
- * @author Vova Feldman (@svovaf)
1122
- * @since 1.2.3
1123
- *
1124
- * @param string $text Translatable string.
1125
- * @param string $key String key for overrides.
1126
- * @param string $slug Module slug for overrides.
1127
- *
1128
- * @return string
1129
- */
1130
- function fs_esc_js_inline( $text, $key = '', $slug = 'freemius' ) {
1131
- return esc_js( _fs_text_inline( $text, $key, $slug ) );
1132
- }
1133
- }
1134
-
1135
- if ( ! function_exists( 'fs_esc_js_x_inline' ) ) {
1136
- /**
1137
- * @author Vova Feldman (@svovaf)
1138
- * @since 1.2.3
1139
- *
1140
- * @param string $text Translatable string.
1141
- * @param string $context Context information for the translators.
1142
- * @param string $key String key for overrides.
1143
- * @param string $slug Module slug for overrides.
1144
- *
1145
- * @return string
1146
- */
1147
- function fs_esc_js_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1148
- return esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) );
1149
- }
1150
- }
1151
-
1152
- if ( ! function_exists( 'fs_esc_js_echo_x_inline' ) ) {
1153
- /**
1154
- * @author Vova Feldman (@svovaf)
1155
- * @since 1.2.3
1156
- *
1157
- * @param string $text Translatable string.
1158
- * @param string $context Context information for the translators.
1159
- * @param string $key String key for overrides.
1160
- * @param string $slug Module slug for overrides.
1161
- *
1162
- * @return string
1163
- */
1164
- function fs_esc_js_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1165
- echo esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) );
1166
- }
1167
- }
1168
-
1169
- if ( ! function_exists( 'fs_esc_js_echo' ) ) {
1170
- /**
1171
- * @author Vova Feldman
1172
- * @since 1.2.1.6
1173
- *
1174
- * @param string $key
1175
- * @param string $slug
1176
- */
1177
- function fs_esc_js_echo( $key, $slug ) {
1178
- echo esc_js( fs_text( $key, $slug ) );
1179
- }
1180
- }
1181
-
1182
- if ( ! function_exists( 'fs_esc_js_echo_inline' ) ) {
1183
- /**
1184
- * @author Vova Feldman (@svovaf)
1185
- * @since 1.2.3
1186
- *
1187
- * @param string $text Translatable string.
1188
- * @param string $key String key for overrides.
1189
- * @param string $slug Module slug for overrides.
1190
- */
1191
- function fs_esc_js_echo_inline( $text, $key = '', $slug = 'freemius' ) {
1192
- echo esc_js( _fs_text_inline( $text, $key, $slug ) );
1193
- }
1194
- }
1195
-
1196
- if ( ! function_exists( 'fs_json_encode_echo' ) ) {
1197
- /**
1198
- * @author Vova Feldman
1199
- * @since 1.2.1.6
1200
- *
1201
- * @param string $key
1202
- * @param string $slug
1203
- */
1204
- function fs_json_encode_echo( $key, $slug ) {
1205
- echo json_encode( fs_text( $key, $slug ) );
1206
- }
1207
- }
1208
-
1209
- if ( ! function_exists( 'fs_json_encode_echo_inline' ) ) {
1210
- /**
1211
- * @author Vova Feldman (@svovaf)
1212
- * @since 1.2.3
1213
- *
1214
- * @param string $text Translatable string.
1215
- * @param string $key String key for overrides.
1216
- * @param string $slug Module slug for overrides.
1217
- */
1218
- function fs_json_encode_echo_inline( $text, $key = '', $slug = 'freemius' ) {
1219
- echo json_encode( _fs_text_inline( $text, $key, $slug ) );
1220
- }
1221
- }
1222
-
1223
- if ( ! function_exists( 'fs_esc_html' ) ) {
1224
- /**
1225
- * @author Vova Feldman
1226
- * @since 1.2.1.6
1227
- *
1228
- * @param string $key
1229
- * @param string $slug
1230
- *
1231
- * @return string
1232
- */
1233
- function fs_esc_html( $key, $slug ) {
1234
- return esc_html( fs_text( $key, $slug ) );
1235
- }
1236
- }
1237
-
1238
- if ( ! function_exists( 'fs_esc_html_inline' ) ) {
1239
- /**
1240
- * @author Vova Feldman (@svovaf)
1241
- * @since 1.2.3
1242
- *
1243
- * @param string $text Translatable string.
1244
- * @param string $key String key for overrides.
1245
- * @param string $slug Module slug for overrides.
1246
- *
1247
- * @return string
1248
- */
1249
- function fs_esc_html_inline( $text, $key = '', $slug = 'freemius' ) {
1250
- return esc_html( _fs_text_inline( $text, $key, $slug ) );
1251
- }
1252
- }
1253
-
1254
- if ( ! function_exists( 'fs_esc_html_x_inline' ) ) {
1255
- /**
1256
- * @author Vova Feldman (@svovaf)
1257
- * @since 1.2.3
1258
- *
1259
- * @param string $text Translatable string.
1260
- * @param string $context Context information for the translators.
1261
- * @param string $key String key for overrides.
1262
- * @param string $slug Module slug for overrides.
1263
- *
1264
- * @return string
1265
- */
1266
- function fs_esc_html_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1267
- return esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) );
1268
- }
1269
- }
1270
-
1271
- if ( ! function_exists( 'fs_esc_html_echo_x_inline' ) ) {
1272
- /**
1273
- * @author Vova Feldman (@svovaf)
1274
- * @since 1.2.3
1275
- *
1276
- * @param string $text Translatable string.
1277
- * @param string $context Context information for the translators.
1278
- * @param string $key String key for overrides.
1279
- * @param string $slug Module slug for overrides.
1280
- */
1281
- function fs_esc_html_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1282
- echo esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) );
1283
- }
1284
- }
1285
-
1286
- if ( ! function_exists( 'fs_esc_html_echo' ) ) {
1287
- /**
1288
- * @author Vova Feldman
1289
- * @since 1.2.1.6
1290
- *
1291
- * @param string $key
1292
- * @param string $slug
1293
- */
1294
- function fs_esc_html_echo( $key, $slug ) {
1295
- echo esc_html( fs_text( $key, $slug ) );
1296
- }
1297
- }
1298
-
1299
- if ( ! function_exists( 'fs_esc_html_echo_inline' ) ) {
1300
- /**
1301
- * @author Vova Feldman (@svovaf)
1302
- * @since 1.2.3
1303
- *
1304
- * @param string $text Translatable string.
1305
- * @param string $key String key for overrides.
1306
- * @param string $slug Module slug for overrides.
1307
- */
1308
- function fs_esc_html_echo_inline( $text, $key = '', $slug = 'freemius' ) {
1309
- echo esc_html( _fs_text_inline( $text, $key, $slug ) );
1310
- }
1311
- }
1312
-
1313
- if ( ! function_exists( 'fs_override_i18n' ) ) {
1314
- /**
1315
- * Override default i18n text phrases.
1316
- *
1317
- * @author Vova Feldman (@svovaf)
1318
- * @since 1.1.6
1319
- *
1320
- * @param array[string]string $key_value
1321
- * @param string $slug
1322
- *
1323
- * @global $fs_text_overrides
1324
- */
1325
- function fs_override_i18n( array $key_value, $slug = 'freemius' ) {
1326
- global $fs_text_overrides;
1327
-
1328
- if ( ! isset( $fs_text_overrides[ $slug ] ) ) {
1329
- $fs_text_overrides[ $slug ] = array();
1330
- }
1331
-
1332
- foreach ( $key_value as $key => $value ) {
1333
- $fs_text_overrides[ $slug ][ $key ] = $value;
1334
- }
1335
- }
1336
- }
1337
-
1338
- #endregion
1339
-
1340
- #--------------------------------------------------------------------------------
1341
- #region Multisite Network
1342
- #--------------------------------------------------------------------------------
1343
-
1344
- if ( ! function_exists( 'fs_is_plugin_uninstall' ) ) {
1345
- /**
1346
- * @author Vova Feldman (@svovaf)
1347
- * @since 2.0.0
1348
- */
1349
- function fs_is_plugin_uninstall() {
1350
- return (
1351
- defined( 'WP_UNINSTALL_PLUGIN' ) ||
1352
- ( 0 < did_action( 'update_option_uninstall_plugins' ) )
1353
- );
1354
- }
1355
- }
1356
-
1357
- if ( ! function_exists( 'fs_is_network_admin' ) ) {
1358
- /**
1359
- * Unlike is_network_admin(), this one will also work properly when
1360
- * the context execution is WP AJAX handler, and during plugin
1361
- * uninstall.
1362
- *
1363
- * @author Vova Feldman (@svovaf)
1364
- * @since 2.0.0
1365
- */
1366
- function fs_is_network_admin() {
1367
- return (
1368
- WP_FS__IS_NETWORK_ADMIN ||
1369
- ( is_multisite() && fs_is_plugin_uninstall() )
1370
- );
1371
- }
1372
- }
1373
-
1374
- if ( ! function_exists( 'fs_is_blog_admin' ) ) {
1375
- /**
1376
- * Unlike is_blog_admin(), this one will also work properly when
1377
- * the context execution is WP AJAX handler, and during plugin
1378
- * uninstall.
1379
- *
1380
- * @author Vova Feldman (@svovaf)
1381
- * @since 2.0.0
1382
- */
1383
- function fs_is_blog_admin() {
1384
- return (
1385
- WP_FS__IS_BLOG_ADMIN ||
1386
- ( ! is_multisite() && fs_is_plugin_uninstall() )
1387
- );
1388
- }
1389
- }
1390
-
1391
- #endregion
1392
-
1393
- if ( ! function_exists( 'fs_apply_filter' ) ) {
1394
- /**
1395
- * Apply filter for specific plugin.
1396
- *
1397
- * @author Vova Feldman (@svovaf)
1398
- * @since 1.0.9
1399
- *
1400
- * @param string $module_unique_affix Module's unique affix.
1401
- * @param string $tag The name of the filter hook.
1402
- * @param mixed $value The value on which the filters hooked to `$tag` are applied on.
1403
- *
1404
- * @return mixed The filtered value after all hooked functions are applied to it.
1405
- *
1406
- * @uses apply_filters()
1407
- */
1408
- function fs_apply_filter( $module_unique_affix, $tag, $value ) {
1409
- $args = func_get_args();
1410
-
1411
- return call_user_func_array( 'apply_filters', array_merge(
1412
- array( "fs_{$tag}_{$module_unique_affix}" ),
1413
- array_slice( $args, 2 ) )
1414
- );
1415
- }
1416
  }
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.0.3
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ if ( ! function_exists( 'fs_dummy' ) ) {
14
+ function fs_dummy() {
15
+ }
16
+ }
17
+
18
+ /* Url.
19
+ --------------------------------------------------------------------------------------------*/
20
+ if ( ! function_exists( 'fs_get_url_daily_cache_killer' ) ) {
21
+ function fs_get_url_daily_cache_killer() {
22
+ return date( '\YY\Mm\Dd' );
23
+ }
24
+ }
25
+
26
+ /* Templates / Views.
27
+ --------------------------------------------------------------------------------------------*/
28
+ if ( ! function_exists( 'fs_get_template_path' ) ) {
29
+ function fs_get_template_path( $path ) {
30
+ return WP_FS__DIR_TEMPLATES . '/' . trim( $path, '/' );
31
+ }
32
+
33
+ function fs_include_template( $path, &$params = null ) {
34
+ $VARS = &$params;
35
+ include fs_get_template_path( $path );
36
+ }
37
+
38
+ function fs_include_once_template( $path, &$params = null ) {
39
+ $VARS = &$params;
40
+ include_once fs_get_template_path( $path );
41
+ }
42
+
43
+ function fs_require_template( $path, &$params = null ) {
44
+ $VARS = &$params;
45
+ require fs_get_template_path( $path );
46
+ }
47
+
48
+ function fs_require_once_template( $path, &$params = null ) {
49
+ $VARS = &$params;
50
+ require_once fs_get_template_path( $path );
51
+ }
52
+
53
+ function fs_get_template( $path, &$params = null ) {
54
+ ob_start();
55
+
56
+ $VARS = &$params;
57
+ require fs_get_template_path( $path );
58
+
59
+ return ob_get_clean();
60
+ }
61
+ }
62
+
63
+ /* Scripts and styles including.
64
+ --------------------------------------------------------------------------------------------*/
65
+
66
+ if ( ! function_exists( 'fs_asset_url' ) ) {
67
+ /**
68
+ * Generates an absolute URL to the given path. This function ensures that the URL will be correct whether the asset
69
+ * is inside a plugin's folder or a theme's folder.
70
+ *
71
+ * Examples:
72
+ * 1. "themes" folder
73
+ * Path: C:/xampp/htdocs/fswp/wp-content/themes/twentytwelve/freemius/assets/css/admin/common.css
74
+ * URL: http://fswp:8080/wp-content/themes/twentytwelve/freemius/assets/css/admin/common.css
75
+ *
76
+ * 2. "plugins" folder
77
+ * Path: C:/xampp/htdocs/fswp/wp-content/plugins/rating-widget-premium/freemius/assets/css/admin/common.css
78
+ * URL: http://fswp:8080/wp-content/plugins/rating-widget-premium/freemius/assets/css/admin/common.css
79
+ *
80
+ * @author Leo Fajardo (@leorw)
81
+ * @since 1.2.2
82
+ *
83
+ * @param string $asset_abs_path Asset's absolute path.
84
+ *
85
+ * @return string Asset's URL.
86
+ */
87
+ function fs_asset_url( $asset_abs_path ) {
88
+ $wp_content_dir = fs_normalize_path( WP_CONTENT_DIR );
89
+ $asset_abs_path = fs_normalize_path( $asset_abs_path );
90
+
91
+ if ( 0 === strpos( $asset_abs_path, $wp_content_dir ) ) {
92
+ // Handle both theme and plugin assets located in the standard directories.
93
+ $asset_rel_path = str_replace( $wp_content_dir, '', $asset_abs_path );
94
+ $asset_url = content_url( fs_normalize_path( $asset_rel_path ) );
95
+ } else {
96
+ $wp_plugins_dir = fs_normalize_path( WP_PLUGIN_DIR );
97
+ if ( 0 === strpos( $asset_abs_path, $wp_plugins_dir ) ) {
98
+ // Try to handle plugin assets that may be located in a non-standard plugins directory.
99
+ $asset_rel_path = str_replace( $wp_plugins_dir, '', $asset_abs_path );
100
+ $asset_url = plugins_url( fs_normalize_path( $asset_rel_path ) );
101
+ } else {
102
+ // Try to handle theme assets that may be located in a non-standard themes directory.
103
+ $active_theme_stylesheet = get_stylesheet();
104
+ $wp_themes_dir = fs_normalize_path( trailingslashit( get_theme_root( $active_theme_stylesheet ) ) );
105
+ $asset_rel_path = str_replace( $wp_themes_dir, '', fs_normalize_path( $asset_abs_path ) );
106
+ $asset_url = trailingslashit( get_theme_root_uri( $active_theme_stylesheet ) ) . fs_normalize_path( $asset_rel_path );
107
+ }
108
+ }
109
+
110
+ return $asset_url;
111
+ }
112
+ }
113
+
114
+ if ( ! function_exists( 'fs_enqueue_local_style' ) ) {
115
+ function fs_enqueue_local_style( $handle, $path, $deps = array(), $ver = false, $media = 'all' ) {
116
+ wp_enqueue_style( $handle, fs_asset_url( WP_FS__DIR_CSS . '/' . trim( $path, '/' ) ), $deps, $ver, $media );
117
+ }
118
+ }
119
+
120
+ if ( ! function_exists( 'fs_enqueue_local_script' ) ) {
121
+ function fs_enqueue_local_script( $handle, $path, $deps = array(), $ver = false, $in_footer = 'all' ) {
122
+ wp_enqueue_script( $handle, fs_asset_url( WP_FS__DIR_JS . '/' . trim( $path, '/' ) ), $deps, $ver, $in_footer );
123
+ }
124
+ }
125
+
126
+ if ( ! function_exists( 'fs_img_url' ) ) {
127
+ function fs_img_url( $path, $img_dir = WP_FS__DIR_IMG ) {
128
+ return ( fs_asset_url( $img_dir . '/' . trim( $path, '/' ) ) );
129
+ }
130
+ }
131
+
132
+ #--------------------------------------------------------------------------------
133
+ #region Request handlers.
134
+ #--------------------------------------------------------------------------------
135
+
136
+ if ( ! function_exists( 'fs_request_get' ) ) {
137
+ /**
138
+ * A helper method to fetch GET/POST user input with an optional default value when the input is not set.
139
+ * @author Vova Feldman (@svovaf)
140
+ *
141
+ * @param string $key
142
+ * @param mixed $def
143
+ * @param string|bool $type Since 1.2.1.7 - when set to 'get' will look for the value passed via querystring, when
144
+ * set to 'post' will look for the value passed via the POST request's body, otherwise,
145
+ * will check if the parameter was passed in any of the two.
146
+ *
147
+ * @return mixed
148
+ */
149
+ function fs_request_get( $key, $def = false, $type = false ) {
150
+ if ( is_string( $type ) ) {
151
+ $type = strtolower( $type );
152
+ }
153
+
154
+ /**
155
+ * Note to WordPress.org Reviewers:
156
+ * This is a helper method to fetch GET/POST user input with an optional default value when the input is not set. The actual sanitization is done in the scope of the function's usage.
157
+ */
158
+ switch ( $type ) {
159
+ case 'post':
160
+ $value = isset( $_POST[ $key ] ) ? $_POST[ $key ] : $def;
161
+ break;
162
+ case 'get':
163
+ $value = isset( $_GET[ $key ] ) ? $_GET[ $key ] : $def;
164
+ break;
165
+ default:
166
+ $value = isset( $_REQUEST[ $key ] ) ? $_REQUEST[ $key ] : $def;
167
+ break;
168
+ }
169
+
170
+ return $value;
171
+ }
172
+ }
173
+
174
+ if ( ! function_exists( 'fs_request_has' ) ) {
175
+ function fs_request_has( $key ) {
176
+ return isset( $_REQUEST[ $key ] );
177
+ }
178
+ }
179
+
180
+ if ( ! function_exists( 'fs_request_get_bool' ) ) {
181
+ /**
182
+ * A helper method to fetch GET/POST user boolean input with an optional default value when the input is not set.
183
+ *
184
+ * @author Vova Feldman (@svovaf)
185
+ *
186
+ * @param string $key
187
+ * @param bool $def
188
+ *
189
+ * @return bool|mixed
190
+ */
191
+ function fs_request_get_bool( $key, $def = false ) {
192
+ $val = fs_request_get( $key, null );
193
+
194
+ if ( is_null( $val ) ) {
195
+ return $def;
196
+ }
197
+
198
+ if ( is_bool( $val ) ) {
199
+ return $val;
200
+ } else if ( is_numeric( $val ) ) {
201
+ if ( 1 == $val ) {
202
+ return true;
203
+ } else if ( 0 == $val ) {
204
+ return false;
205
+ }
206
+ } else if ( is_string( $val ) ) {
207
+ $val = strtolower( $val );
208
+
209
+ if ( 'true' === $val ) {
210
+ return true;
211
+ } else if ( 'false' === $val ) {
212
+ return false;
213
+ }
214
+ }
215
+
216
+ return $def;
217
+ }
218
+ }
219
+
220
+ if ( ! function_exists( 'fs_request_is_post' ) ) {
221
+ function fs_request_is_post() {
222
+ return ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) );
223
+ }
224
+ }
225
+
226
+ if ( ! function_exists( 'fs_request_is_get' ) ) {
227
+ function fs_request_is_get() {
228
+ return ( 'get' === strtolower( $_SERVER['REQUEST_METHOD'] ) );
229
+ }
230
+ }
231
+
232
+ if ( ! function_exists( 'fs_get_action' ) ) {
233
+ function fs_get_action( $action_key = 'action' ) {
234
+ if ( ! empty( $_REQUEST[ $action_key ] ) && is_string( $_REQUEST[ $action_key ] ) ) {
235
+ return strtolower( $_REQUEST[ $action_key ] );
236
+ }
237
+
238
+ if ( 'action' == $action_key ) {
239
+ $action_key = 'fs_action';
240
+
241
+ if ( ! empty( $_REQUEST[ $action_key ] ) && is_string( $_REQUEST[ $action_key ] ) ) {
242
+ return strtolower( $_REQUEST[ $action_key ] );
243
+ }
244
+ }
245
+
246
+ return false;
247
+ }
248
+ }
249
+
250
+ if ( ! function_exists( 'fs_request_is_action' ) ) {
251
+ function fs_request_is_action( $action, $action_key = 'action' ) {
252
+ return ( strtolower( $action ) === fs_get_action( $action_key ) );
253
+ }
254
+ }
255
+
256
+ if ( ! function_exists( 'fs_request_is_action_secure' ) ) {
257
+ /**
258
+ * @author Vova Feldman (@svovaf)
259
+ * @since 1.0.0
260
+ *
261
+ * @since 1.2.1.5 Allow nonce verification.
262
+ *
263
+ * @param string $action
264
+ * @param string $action_key
265
+ * @param string $nonce_key
266
+ *
267
+ * @return bool
268
+ */
269
+ function fs_request_is_action_secure(
270
+ $action,
271
+ $action_key = 'action',
272
+ $nonce_key = 'nonce'
273
+ ) {
274
+ if ( strtolower( $action ) !== fs_get_action( $action_key ) ) {
275
+ return false;
276
+ }
277
+
278
+ $nonce = ! empty( $_REQUEST[ $nonce_key ] ) ?
279
+ $_REQUEST[ $nonce_key ] :
280
+ '';
281
+
282
+ if ( empty( $nonce ) ||
283
+ ( false === wp_verify_nonce( $nonce, $action ) )
284
+ ) {
285
+ return false;
286
+ }
287
+
288
+ return true;
289
+ }
290
+ }
291
+
292
+ #endregion
293
+
294
+ if ( ! function_exists( 'fs_is_plugin_page' ) ) {
295
+ function fs_is_plugin_page( $page_slug ) {
296
+ return ( is_admin() && $page_slug === fs_request_get( 'page' ) );
297
+ }
298
+ }
299
+
300
+ if ( ! function_exists( 'fs_get_raw_referer' ) ) {
301
+ /**
302
+ * Retrieves unvalidated referer from '_wp_http_referer' or HTTP referer.
303
+ *
304
+ * Do not use for redirects, use {@see wp_get_referer()} instead.
305
+ *
306
+ * @since 1.2.3
307
+ *
308
+ * @return string|false Referer URL on success, false on failure.
309
+ */
310
+ function fs_get_raw_referer() {
311
+ if ( function_exists( 'wp_get_raw_referer' ) ) {
312
+ return wp_get_raw_referer();
313
+ }
314
+ if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
315
+ return wp_unslash( $_REQUEST['_wp_http_referer'] );
316
+ } else if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) {
317
+ return wp_unslash( $_SERVER['HTTP_REFERER'] );
318
+ }
319
+
320
+ return false;
321
+ }
322
+ }
323
+
324
+ /* Core UI.
325
+ --------------------------------------------------------------------------------------------*/
326
+ if ( ! function_exists( 'fs_ui_action_button' ) ) {
327
+ /**
328
+ * @param number $module_id
329
+ * @param string $page
330
+ * @param string $action
331
+ * @param string $title
332
+ * @param string $button_class
333
+ * @param array $params
334
+ * @param bool $is_primary
335
+ * @param bool $is_small
336
+ * @param string|bool $icon_class Optional class for an icon (since 1.1.7).
337
+ * @param string|bool $confirmation Optional confirmation message before submit (since 1.1.7).
338
+ * @param string $method Since 1.1.7
339
+ *
340
+ * @uses fs_ui_get_action_button()
341
+ */
342
+ function fs_ui_action_button(
343
+ $module_id,
344
+ $page,
345
+ $action,
346
+ $title,
347
+ $button_class = '',
348
+ $params = array(),
349
+ $is_primary = true,
350
+ $is_small = false,
351
+ $icon_class = false,
352
+ $confirmation = false,
353
+ $method = 'GET'
354
+ ) {
355
+ echo fs_ui_get_action_button(
356
+ $module_id,
357
+ $page,
358
+ $action,
359
+ $title,
360
+ $button_class,
361
+ $params,
362
+ $is_primary,
363
+ $is_small,
364
+ $icon_class,
365
+ $confirmation,
366
+ $method
367
+ );
368
+ }
369
+ }
370
+
371
+ if ( ! function_exists( 'fs_ui_get_action_button' ) ) {
372
+ /**
373
+ * @author Vova Feldman (@svovaf)
374
+ * @since 1.1.7
375
+ *
376
+ * @param number $module_id
377
+ * @param string $page
378
+ * @param string $action
379
+ * @param string $title
380
+ * @param string $button_class
381
+ * @param array $params
382
+ * @param bool $is_primary
383
+ * @param bool $is_small
384
+ * @param string|bool $icon_class Optional class for an icon.
385
+ * @param string|bool $confirmation Optional confirmation message before submit.
386
+ * @param string $method
387
+ *
388
+ * @return string
389
+ */
390
+ function fs_ui_get_action_button(
391
+ $module_id,
392
+ $page,
393
+ $action,
394
+ $title,
395
+ $button_class = '',
396
+ $params = array(),
397
+ $is_primary = true,
398
+ $is_small = false,
399
+ $icon_class = false,
400
+ $confirmation = false,
401
+ $method = 'GET'
402
+ ) {
403
+ // Prepend icon (if set).
404
+ $title = ( is_string( $icon_class ) ? '<i class="' . $icon_class . '"></i> ' : '' ) . $title;
405
+
406
+ if ( is_string( $confirmation ) ) {
407
+ return sprintf( '<form action="%s" method="%s"><input type="hidden" name="fs_action" value="%s">%s<a href="#" class="%s" onclick="if (confirm(\'%s\')) this.parentNode.submit(); return false;">%s</a></form>',
408
+ freemius( $module_id )->_get_admin_page_url( $page, $params ),
409
+ $method,
410
+ $action,
411
+ wp_nonce_field( $action, '_wpnonce', true, false ),
412
+ 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ),
413
+ $confirmation,
414
+ $title
415
+ );
416
+ } else if ( 'GET' !== strtoupper( $method ) ) {
417
+ return sprintf( '<form action="%s" method="%s"><input type="hidden" name="fs_action" value="%s">%s<a href="#" class="%s" onclick="this.parentNode.submit(); return false;">%s</a></form>',
418
+ freemius( $module_id )->_get_admin_page_url( $page, $params ),
419
+ $method,
420
+ $action,
421
+ wp_nonce_field( $action, '_wpnonce', true, false ),
422
+ 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ),
423
+ $title
424
+ );
425
+ } else {
426
+ return sprintf( '<a href="%s" class="%s">%s</a></form>',
427
+ wp_nonce_url( freemius( $module_id )->_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ),
428
+ 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ),
429
+ $title
430
+ );
431
+ }
432
+ }
433
+
434
+ function fs_ui_action_link( $module_id, $page, $action, $title, $params = array() ) {
435
+ ?><a class=""
436
+ href="<?php echo wp_nonce_url( freemius( $module_id )->_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ) ?>"><?php echo $title ?></a><?php
437
+ }
438
+ }
439
+
440
+ if ( ! function_exists( 'fs_get_entity' ) ) {
441
+ /**
442
+ * @author Leo Fajardo (@leorw)
443
+ * @since 2.3.1
444
+ *
445
+ * @param mixed $entity
446
+ * @param string $class
447
+ *
448
+ * @return FS_Plugin|FS_User|FS_Site|FS_Plugin_License|FS_Plugin_Plan|FS_Plugin_Tag|FS_Subscription
449
+ */
450
+ function fs_get_entity( $entity, $class ) {
451
+ if ( ! is_object( $entity ) || $entity instanceof $class ) {
452
+ return $entity;
453
+ }
454
+
455
+ return new $class( $entity );
456
+ }
457
+ }
458
+
459
+ if ( ! function_exists( 'fs_get_entities' ) ) {
460
+ /**
461
+ * @author Leo Fajardo (@leorw)
462
+ * @since 2.3.1
463
+ *
464
+ * @param mixed $entities
465
+ * @param string $class_name
466
+ *
467
+ * @return FS_Plugin[]|FS_User[]|FS_Site[]|FS_Plugin_License[]|FS_Plugin_Plan[]|FS_Plugin_Tag[]|FS_Subscription[]
468
+ */
469
+ function fs_get_entities( $entities, $class_name ) {
470
+ if ( ! is_array( $entities ) || empty( $entities ) ) {
471
+ return $entities;
472
+ }
473
+
474
+ // Get first element.
475
+ $first_array_element = reset( $entities );
476
+
477
+ if ( $first_array_element instanceof $class_name ) {
478
+ /**
479
+ * If the first element of the array is an instance of the context class, assume that all other
480
+ * elements are instances of the class.
481
+ */
482
+ return $entities;
483
+ }
484
+
485
+ if (
486
+ is_array( $first_array_element ) &&
487
+ ! empty( $first_array_element )
488
+ ) {
489
+ $first_array_element = reset( $first_array_element );
490
+
491
+ if ( $first_array_element instanceof $class_name ) {
492
+ /**
493
+ * If the first element of the `$entities` array is an array whose first element is an instance of the
494
+ * context class, assume that all other objects are instances of the class.
495
+ */
496
+ return $entities;
497
+ }
498
+ }
499
+
500
+ foreach ( $entities as $key => $entities_or_entity ) {
501
+ if ( is_array( $entities_or_entity ) ) {
502
+ $entities[ $key ] = fs_get_entities( $entities_or_entity, $class_name );
503
+ } else {
504
+ $entities[ $key ] = fs_get_entity( $entities_or_entity, $class_name );
505
+ }
506
+ }
507
+
508
+ return $entities;
509
+ }
510
+ }
511
+
512
+ if ( ! function_exists( 'fs_nonce_url' ) ) {
513
+ /**
514
+ * Retrieve URL with nonce added to URL query.
515
+ *
516
+ * Originally was using `wp_nonce_url()` but the new version
517
+ * changed the return value to escaped URL, that's not the expected
518
+ * behaviour.
519
+ *
520
+ * @author Vova Feldman (@svovaf)
521
+ * @since ~1.1.3
522
+ *
523
+ * @param string $actionurl URL to add nonce action.
524
+ * @param int|string $action Optional. Nonce action name. Default -1.
525
+ * @param string $name Optional. Nonce name. Default '_wpnonce'.
526
+ *
527
+ * @return string Escaped URL with nonce action added.
528
+ */
529
+ function fs_nonce_url( $actionurl, $action = - 1, $name = '_wpnonce' ) {
530
+ return add_query_arg( $name, wp_create_nonce( $action ), $actionurl );
531
+ }
532
+ }
533
+
534
+ if ( ! function_exists( 'fs_starts_with' ) ) {
535
+ /**
536
+ * Check if string starts with.
537
+ *
538
+ * @author Vova Feldman (@svovaf)
539
+ * @since 1.1.3
540
+ *
541
+ * @param string $haystack
542
+ * @param string $needle
543
+ *
544
+ * @return bool
545
+ */
546
+ function fs_starts_with( $haystack, $needle ) {
547
+ $length = strlen( $needle );
548
+
549
+ return ( substr( $haystack, 0, $length ) === $needle );
550
+ }
551
+ }
552
+
553
+ if ( ! function_exists( 'fs_ends_with' ) ) {
554
+ /**
555
+ * Check if string ends with.
556
+ *
557
+ * @author Vova Feldman (@svovaf)
558
+ * @since 2.0.0
559
+ *
560
+ * @param string $haystack
561
+ * @param string $needle
562
+ *
563
+ * @return bool
564
+ */
565
+ function fs_ends_with( $haystack, $needle ) {
566
+ $length = strlen( $needle );
567
+ $start = $length * - 1; // negative
568
+
569
+ return ( substr( $haystack, $start ) === $needle );
570
+ }
571
+ }
572
+
573
+ if ( ! function_exists( 'fs_strip_url_protocol' ) ) {
574
+ function fs_strip_url_protocol( $url ) {
575
+ if ( ! fs_starts_with( $url, 'http' ) ) {
576
+ return $url;
577
+ }
578
+
579
+ $protocol_pos = strpos( $url, '://' );
580
+
581
+ if ( $protocol_pos > 5 ) {
582
+ return $url;
583
+ }
584
+
585
+ return substr( $url, $protocol_pos + 3 );
586
+ }
587
+ }
588
+
589
+ #region Url Canonization ------------------------------------------------------------------
590
+
591
+ if ( ! function_exists( 'fs_canonize_url' ) ) {
592
+ /**
593
+ * @author Vova Feldman (@svovaf)
594
+ * @since 1.1.3
595
+ *
596
+ * @param string $url
597
+ * @param bool $omit_host
598
+ * @param array $ignore_params
599
+ *
600
+ * @return string
601
+ */
602
+ function fs_canonize_url( $url, $omit_host = false, $ignore_params = array() ) {
603
+ $parsed_url = parse_url( strtolower( $url ) );
604
+
605
+ // if ( ! isset( $parsed_url['host'] ) ) {
606
+ // return $url;
607
+ // }
608
+
609
+ $canonical = ( ( $omit_host || ! isset( $parsed_url['host'] ) ) ? '' : $parsed_url['host'] ) . $parsed_url['path'];
610
+
611
+ if ( isset( $parsed_url['query'] ) ) {
612
+ parse_str( $parsed_url['query'], $queryString );
613
+ $canonical .= '?' . fs_canonize_query_string( $queryString, $ignore_params );
614
+ }
615
+
616
+ return $canonical;
617
+ }
618
+ }
619
+
620
+ if ( ! function_exists( 'fs_canonize_query_string' ) ) {
621
+ /**
622
+ * @author Vova Feldman (@svovaf)
623
+ * @since 1.1.3
624
+ *
625
+ * @param array $params
626
+ * @param array $ignore_params
627
+ * @param bool $params_prefix
628
+ *
629
+ * @return string
630
+ */
631
+ function fs_canonize_query_string( array $params, array &$ignore_params, $params_prefix = false ) {
632
+ if ( ! is_array( $params ) || 0 === count( $params ) ) {
633
+ return '';
634
+ }
635
+
636
+ // Url encode both keys and values
637
+ $keys = fs_urlencode_rfc3986( array_keys( $params ) );
638
+ $values = fs_urlencode_rfc3986( array_values( $params ) );
639
+ $params = array_combine( $keys, $values );
640
+
641
+ // Parameters are sorted by name, using lexicographical byte value ordering.
642
+ // Ref: Spec: 9.1.1 (1)
643
+ uksort( $params, 'strcmp' );
644
+
645
+ $pairs = array();
646
+ foreach ( $params as $parameter => $value ) {
647
+ $lower_param = strtolower( $parameter );
648
+
649
+ // Skip ignore params.
650
+ if ( in_array( $lower_param, $ignore_params ) ||
651
+ ( false !== $params_prefix && fs_starts_with( $lower_param, $params_prefix ) )
652
+ ) {
653
+ continue;
654
+ }
655
+
656
+ if ( is_array( $value ) ) {
657
+ // If two or more parameters share the same name, they are sorted by their value
658
+ // Ref: Spec: 9.1.1 (1)
659
+ natsort( $value );
660
+ foreach ( $value as $duplicate_value ) {
661
+ $pairs[] = $lower_param . '=' . $duplicate_value;
662
+ }
663
+ } else {
664
+ $pairs[] = $lower_param . '=' . $value;
665
+ }
666
+ }
667
+
668
+ if ( 0 === count( $pairs ) ) {
669
+ return '';
670
+ }
671
+
672
+ return implode( "&", $pairs );
673
+ }
674
+ }
675
+
676
+ if ( ! function_exists( 'fs_urlencode_rfc3986' ) ) {
677
+ /**
678
+ * @author Vova Feldman (@svovaf)
679
+ * @since 1.1.3
680
+ *
681
+ * @param string|string[] $input
682
+ *
683
+ * @return array|mixed|string
684
+ */
685
+ function fs_urlencode_rfc3986( $input ) {
686
+ if ( is_array( $input ) ) {
687
+ return array_map( 'fs_urlencode_rfc3986', $input );
688
+ } else if ( is_scalar( $input ) ) {
689
+ return str_replace( '+', ' ', str_replace( '%7E', '~', rawurlencode( $input ) ) );
690
+ }
691
+
692
+ return '';
693
+ }
694
+ }
695
+
696
+ #endregion Url Canonization ------------------------------------------------------------------
697
+
698
+ if ( ! function_exists( 'fs_download_image' ) ) {
699
+ /**
700
+ * @author Vova Feldman (@svovaf)
701
+ *
702
+ * @since 1.2.2 Changed to usage of WP_Filesystem_Direct.
703
+ *
704
+ * @param string $from URL
705
+ * @param string $to File path.
706
+ *
707
+ * @return bool Is successfully downloaded.
708
+ */
709
+ function fs_download_image( $from, $to ) {
710
+ $dir = dirname( $to );
711
+
712
+ if ( 'direct' !== get_filesystem_method( array(), $dir ) ) {
713
+ return false;
714
+ }
715
+
716
+ if ( ! class_exists( 'WP_Filesystem_Direct' ) ) {
717
+ require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
718
+ require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
719
+ }
720
+
721
+ $fs = new WP_Filesystem_Direct( '' );
722
+ $tmpfile = download_url( $from );
723
+
724
+ if ( $tmpfile instanceof WP_Error ) {
725
+ // Issue downloading the file.
726
+ return false;
727
+ }
728
+
729
+ $fs->copy( $tmpfile, $to );
730
+ $fs->delete( $tmpfile );
731
+
732
+ return true;
733
+ }
734
+ }
735
+
736
+ /* General Utilities
737
+ --------------------------------------------------------------------------------------------*/
738
+
739
+ if ( ! function_exists( 'fs_sort_by_priority' ) ) {
740
+ /**
741
+ * Sorts an array by the value of the priority key.
742
+ *
743
+ * @author Daniel Iser (@danieliser)
744
+ * @since 1.1.7
745
+ *
746
+ * @param $a
747
+ * @param $b
748
+ *
749
+ * @return int
750
+ */
751
+ function fs_sort_by_priority( $a, $b ) {
752
+
753
+ // If b has a priority and a does not, b wins.
754
+ if ( ! isset( $a['priority'] ) && isset( $b['priority'] ) ) {
755
+ return 1;
756
+ } // If b has a priority and a does not, b wins.
757
+ elseif ( isset( $a['priority'] ) && ! isset( $b['priority'] ) ) {
758
+ return - 1;
759
+ } // If neither has a priority or both priorities are equal its a tie.
760
+ elseif ( ( ! isset( $a['priority'] ) && ! isset( $b['priority'] ) ) || $a['priority'] === $b['priority'] ) {
761
+ return 0;
762
+ }
763
+
764
+ // If both have priority return the winner.
765
+ return ( $a['priority'] < $b['priority'] ) ? - 1 : 1;
766
+ }
767
+ }
768
+
769
+ #--------------------------------------------------------------------------------
770
+ #region Localization
771
+ #--------------------------------------------------------------------------------
772
+
773
+ if ( ! function_exists( 'fs_text' ) ) {
774
+ /**
775
+ * Retrieve a translated text by key.
776
+ *
777
+ * @author Vova Feldman (@svovaf)
778
+ * @since 1.2.1.7
779
+ *
780
+ * @param string $key
781
+ * @param string $slug
782
+ *
783
+ * @return string
784
+ *
785
+ * @global $fs_text , $fs_text_overrides
786
+ */
787
+ function fs_text( $key, $slug = 'freemius' ) {
788
+ global $fs_text,
789
+ $fs_module_info_text,
790
+ $fs_text_overrides;
791
+
792
+ if ( isset( $fs_text_overrides[ $slug ] ) ) {
793
+ if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) {
794
+ return $fs_text_overrides[ $slug ][ $key ];
795
+ }
796
+
797
+ $lower_key = strtolower( $key );
798
+ if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) {
799
+ return $fs_text_overrides[ $slug ][ $lower_key ];
800
+ }
801
+ }
802
+
803
+ if ( ! isset( $fs_text ) ) {
804
+ $dir = defined( 'WP_FS__DIR_INCLUDES' ) ?
805
+ WP_FS__DIR_INCLUDES :
806
+ dirname( __FILE__ );
807
+
808
+ require_once $dir . '/i18n.php';
809
+ }
810
+
811
+ if ( isset( $fs_text[ $key ] ) ) {
812
+ return $fs_text[ $key ];
813
+ }
814
+
815
+ if ( isset( $fs_module_info_text[ $key ] ) ) {
816
+ return $fs_module_info_text[ $key ];
817
+ }
818
+
819
+ return $key;
820
+ }
821
+
822
+ #region Private
823
+
824
+ /**
825
+ * Retrieve an inline translated text by key with a context.
826
+ *
827
+ * @author Vova Feldman (@svovaf)
828
+ * @since 1.2.3
829
+ *
830
+ * @param string $text Translatable string.
831
+ * @param string $context Context information for the translators.
832
+ * @param string $key String key for overrides.
833
+ * @param string $slug Module slug for overrides.
834
+ *
835
+ * @return string
836
+ *
837
+ * @global $fs_text_overrides
838
+ */
839
+ function _fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
840
+ list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug );
841
+
842
+ // Avoid misleading Theme Check warning.
843
+ $fn = 'translate_with_gettext_context';
844
+
845
+ return $fn( $text, $context, $text_domain );
846
+ }
847
+
848
+ #endregion
849
+
850
+ /**
851
+ * Retrieve an inline translated text by key with a context.
852
+ *
853
+ * @author Vova Feldman (@svovaf)
854
+ * @since 1.2.3
855
+ *
856
+ * @param string $text Translatable string.
857
+ * @param string $context Context information for the translators.
858
+ * @param string $key String key for overrides.
859
+ * @param string $slug Module slug for overrides.
860
+ *
861
+ * @return string
862
+ *
863
+ * @global $fs_text_overrides
864
+ */
865
+ function fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
866
+ return _fs_text_x_inline( $text, $context, $key, $slug );
867
+ }
868
+
869
+ /**
870
+ * Output a translated text by key.
871
+ *
872
+ * @author Vova Feldman (@svovaf)
873
+ * @since 1.2.1.7
874
+ *
875
+ * @param string $key
876
+ * @param string $slug
877
+ */
878
+ function fs_echo( $key, $slug = 'freemius' ) {
879
+ echo fs_text( $key, $slug );
880
+ }
881
+
882
+ /**
883
+ * Output an inline translated text.
884
+ *
885
+ * @author Vova Feldman (@svovaf)
886
+ * @since 1.2.3
887
+ *
888
+ * @param string $text Translatable string.
889
+ * @param string $key String key for overrides.
890
+ * @param string $slug Module slug for overrides.
891
+ */
892
+ function fs_echo_inline( $text, $key = '', $slug = 'freemius' ) {
893
+ echo _fs_text_inline( $text, $key, $slug );
894
+ }
895
+
896
+ /**
897
+ * Output an inline translated text with a context.
898
+ *
899
+ * @author Vova Feldman (@svovaf)
900
+ * @since 1.2.3
901
+ *
902
+ * @param string $text Translatable string.
903
+ * @param string $context Context information for the translators.
904
+ * @param string $key String key for overrides.
905
+ * @param string $slug Module slug for overrides.
906
+ */
907
+ function fs_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
908
+ echo _fs_text_x_inline( $text, $context, $key, $slug );
909
+ }
910
+ }
911
+
912
+ if ( ! function_exists( 'fs_text_override' ) ) {
913
+ /**
914
+ * Get a translatable text override if exists, or `false`.
915
+ *
916
+ * @author Vova Feldman (@svovaf)
917
+ * @since 1.2.1.7
918
+ *
919
+ * @param string $text Translatable string.
920
+ * @param string $key String key for overrides.
921
+ * @param string $slug Module slug for overrides.
922
+ *
923
+ * @return string|false
924
+ */
925
+ function fs_text_override( $text, $key, $slug ) {
926
+ global $fs_text_overrides;
927
+
928
+ /**
929
+ * Check if string is overridden.
930
+ */
931
+ if ( ! isset( $fs_text_overrides[ $slug ] ) ) {
932
+ return false;
933
+ }
934
+
935
+ if ( empty( $key ) ) {
936
+ $key = strtolower( str_replace( ' ', '-', $text ) );
937
+ }
938
+
939
+ if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) {
940
+ return $fs_text_overrides[ $slug ][ $key ];
941
+ }
942
+
943
+ $lower_key = strtolower( $key );
944
+ if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) {
945
+ return $fs_text_overrides[ $slug ][ $lower_key ];
946
+ }
947
+
948
+ return false;
949
+ }
950
+ }
951
+
952
+ if ( ! function_exists( 'fs_text_and_domain' ) ) {
953
+ /**
954
+ * Get a translatable text and its text domain.
955
+ *
956
+ * When the text is overridden by the module, returns the overridden text and the text domain of the module. Otherwise, returns the original text and 'freemius' as the text domain.
957
+ *
958
+ * @author Vova Feldman (@svovaf)
959
+ * @since 1.2.1.7
960
+ *
961
+ * @param string $text Translatable string.
962
+ * @param string $key String key for overrides.
963
+ * @param string $slug Module slug for overrides.
964
+ *
965
+ * @return string[]
966
+ */
967
+ function fs_text_and_domain( $text, $key, $slug ) {
968
+ $override = fs_text_override( $text, $key, $slug );
969
+
970
+ if ( false === $override ) {
971
+ // No override, use FS text domain.
972
+ $text_domain = 'freemius';
973
+ } else {
974
+ // Found an override.
975
+ $text = $override;
976
+ // Use the module's text domain.
977
+ $text_domain = $slug;
978
+ }
979
+
980
+ return array( $text, $text_domain );
981
+ }
982
+ }
983
+
984
+ if ( ! function_exists( '_fs_text_inline' ) ) {
985
+ /**
986
+ * Retrieve an inline translated text by key.
987
+ *
988
+ * @author Vova Feldman (@svovaf)
989
+ * @since 1.2.3
990
+ *
991
+ * @param string $text Translatable string.
992
+ * @param string $key String key for overrides.
993
+ * @param string $slug Module slug for overrides.
994
+ *
995
+ * @return string
996
+ *
997
+ * @global $fs_text_overrides
998
+ */
999
+ function _fs_text_inline( $text, $key = '', $slug = 'freemius' ) {
1000
+ list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug );
1001
+
1002
+ // Avoid misleading Theme Check warning.
1003
+ $fn = 'translate';
1004
+
1005
+ return $fn( $text, $text_domain );
1006
+ }
1007
+ }
1008
+
1009
+ if ( ! function_exists( 'fs_text_inline' ) ) {
1010
+ /**
1011
+ * Retrieve an inline translated text by key.
1012
+ *
1013
+ * @author Vova Feldman (@svovaf)
1014
+ * @since 1.2.3
1015
+ *
1016
+ * @param string $text Translatable string.
1017
+ * @param string $key String key for overrides.
1018
+ * @param string $slug Module slug for overrides.
1019
+ *
1020
+ * @return string
1021
+ *
1022
+ * @global $fs_text_overrides
1023
+ */
1024
+ function fs_text_inline( $text, $key = '', $slug = 'freemius' ) {
1025
+ return _fs_text_inline( $text, $key, $slug );
1026
+ }
1027
+ }
1028
+
1029
+ if ( ! function_exists( 'fs_esc_attr' ) ) {
1030
+ /**
1031
+ * @author Vova Feldman
1032
+ * @since 1.2.1.6
1033
+ *
1034
+ * @param string $key
1035
+ * @param string $slug
1036
+ *
1037
+ * @return string
1038
+ */
1039
+ function fs_esc_attr( $key, $slug ) {
1040
+ return esc_attr( fs_text( $key, $slug ) );
1041
+ }
1042
+ }
1043
+
1044
+ if ( ! function_exists( 'fs_esc_attr_inline' ) ) {
1045
+ /**
1046
+ * @author Vova Feldman (@svovaf)
1047
+ * @since 1.2.3
1048
+ *
1049
+ * @param string $text Translatable string.
1050
+ * @param string $key String key for overrides.
1051
+ * @param string $slug Module slug for overrides.
1052
+ *
1053
+ * @return string
1054
+ */
1055
+ function fs_esc_attr_inline( $text, $key = '', $slug = 'freemius' ) {
1056
+ return esc_attr( _fs_text_inline( $text, $key, $slug ) );
1057
+ }
1058
+ }
1059
+
1060
+ if ( ! function_exists( 'fs_esc_attr_x_inline' ) ) {
1061
+ /**
1062
+ * @author Vova Feldman (@svovaf)
1063
+ * @since 1.2.3
1064
+ *
1065
+ * @param string $text Translatable string.
1066
+ * @param string $context Context information for the translators.
1067
+ * @param string $key String key for overrides.
1068
+ * @param string $slug Module slug for overrides.
1069
+ *
1070
+ * @return string
1071
+ */
1072
+ function fs_esc_attr_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1073
+ return esc_attr( _fs_text_x_inline( $text, $context, $key, $slug ) );
1074
+ }
1075
+ }
1076
+
1077
+ if ( ! function_exists( 'fs_esc_attr_echo' ) ) {
1078
+ /**
1079
+ * @author Vova Feldman
1080
+ * @since 1.2.1.6
1081
+ *
1082
+ * @param string $key
1083
+ * @param string $slug
1084
+ */
1085
+ function fs_esc_attr_echo( $key, $slug ) {
1086
+ echo esc_attr( fs_text( $key, $slug ) );
1087
+ }
1088
+ }
1089
+
1090
+ if ( ! function_exists( 'fs_esc_attr_echo_inline' ) ) {
1091
+ /**
1092
+ * @author Vova Feldman (@svovaf)
1093
+ * @since 1.2.3
1094
+ *
1095
+ * @param string $text Translatable string.
1096
+ * @param string $key String key for overrides.
1097
+ * @param string $slug Module slug for overrides.
1098
+ */
1099
+ function fs_esc_attr_echo_inline( $text, $key = '', $slug = 'freemius' ) {
1100
+ echo esc_attr( _fs_text_inline( $text, $key, $slug ) );
1101
+ }
1102
+ }
1103
+
1104
+ if ( ! function_exists( 'fs_esc_js' ) ) {
1105
+ /**
1106
+ * @author Vova Feldman
1107
+ * @since 1.2.1.6
1108
+ *
1109
+ * @param string $key
1110
+ * @param string $slug
1111
+ *
1112
+ * @return string
1113
+ */
1114
+ function fs_esc_js( $key, $slug ) {
1115
+ return esc_js( fs_text( $key, $slug ) );
1116
+ }
1117
+ }
1118
+
1119
+ if ( ! function_exists( 'fs_esc_js_inline' ) ) {
1120
+ /**
1121
+ * @author Vova Feldman (@svovaf)
1122
+ * @since 1.2.3
1123
+ *
1124
+ * @param string $text Translatable string.
1125
+ * @param string $key String key for overrides.
1126
+ * @param string $slug Module slug for overrides.
1127
+ *
1128
+ * @return string
1129
+ */
1130
+ function fs_esc_js_inline( $text, $key = '', $slug = 'freemius' ) {
1131
+ return esc_js( _fs_text_inline( $text, $key, $slug ) );
1132
+ }
1133
+ }
1134
+
1135
+ if ( ! function_exists( 'fs_esc_js_x_inline' ) ) {
1136
+ /**
1137
+ * @author Vova Feldman (@svovaf)
1138
+ * @since 1.2.3
1139
+ *
1140
+ * @param string $text Translatable string.
1141
+ * @param string $context Context information for the translators.
1142
+ * @param string $key String key for overrides.
1143
+ * @param string $slug Module slug for overrides.
1144
+ *
1145
+ * @return string
1146
+ */
1147
+ function fs_esc_js_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1148
+ return esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) );
1149
+ }
1150
+ }
1151
+
1152
+ if ( ! function_exists( 'fs_esc_js_echo_x_inline' ) ) {
1153
+ /**
1154
+ * @author Vova Feldman (@svovaf)
1155
+ * @since 1.2.3
1156
+ *
1157
+ * @param string $text Translatable string.
1158
+ * @param string $context Context information for the translators.
1159
+ * @param string $key String key for overrides.
1160
+ * @param string $slug Module slug for overrides.
1161
+ *
1162
+ * @return string
1163
+ */
1164
+ function fs_esc_js_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1165
+ echo esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) );
1166
+ }
1167
+ }
1168
+
1169
+ if ( ! function_exists( 'fs_esc_js_echo' ) ) {
1170
+ /**
1171
+ * @author Vova Feldman
1172
+ * @since 1.2.1.6
1173
+ *
1174
+ * @param string $key
1175
+ * @param string $slug
1176
+ */
1177
+ function fs_esc_js_echo( $key, $slug ) {
1178
+ echo esc_js( fs_text( $key, $slug ) );
1179
+ }
1180
+ }
1181
+
1182
+ if ( ! function_exists( 'fs_esc_js_echo_inline' ) ) {
1183
+ /**
1184
+ * @author Vova Feldman (@svovaf)
1185
+ * @since 1.2.3
1186
+ *
1187
+ * @param string $text Translatable string.
1188
+ * @param string $key String key for overrides.
1189
+ * @param string $slug Module slug for overrides.
1190
+ */
1191
+ function fs_esc_js_echo_inline( $text, $key = '', $slug = 'freemius' ) {
1192
+ echo esc_js( _fs_text_inline( $text, $key, $slug ) );
1193
+ }
1194
+ }
1195
+
1196
+ if ( ! function_exists( 'fs_json_encode_echo' ) ) {
1197
+ /**
1198
+ * @author Vova Feldman
1199
+ * @since 1.2.1.6
1200
+ *
1201
+ * @param string $key
1202
+ * @param string $slug
1203
+ */
1204
+ function fs_json_encode_echo( $key, $slug ) {
1205
+ echo json_encode( fs_text( $key, $slug ) );
1206
+ }
1207
+ }
1208
+
1209
+ if ( ! function_exists( 'fs_json_encode_echo_inline' ) ) {
1210
+ /**
1211
+ * @author Vova Feldman (@svovaf)
1212
+ * @since 1.2.3
1213
+ *
1214
+ * @param string $text Translatable string.
1215
+ * @param string $key String key for overrides.
1216
+ * @param string $slug Module slug for overrides.
1217
+ */
1218
+ function fs_json_encode_echo_inline( $text, $key = '', $slug = 'freemius' ) {
1219
+ echo json_encode( _fs_text_inline( $text, $key, $slug ) );
1220
+ }
1221
+ }
1222
+
1223
+ if ( ! function_exists( 'fs_esc_html' ) ) {
1224
+ /**
1225
+ * @author Vova Feldman
1226
+ * @since 1.2.1.6
1227
+ *
1228
+ * @param string $key
1229
+ * @param string $slug
1230
+ *
1231
+ * @return string
1232
+ */
1233
+ function fs_esc_html( $key, $slug ) {
1234
+ return esc_html( fs_text( $key, $slug ) );
1235
+ }
1236
+ }
1237
+
1238
+ if ( ! function_exists( 'fs_esc_html_inline' ) ) {
1239
+ /**
1240
+ * @author Vova Feldman (@svovaf)
1241
+ * @since 1.2.3
1242
+ *
1243
+ * @param string $text Translatable string.
1244
+ * @param string $key String key for overrides.
1245
+ * @param string $slug Module slug for overrides.
1246
+ *
1247
+ * @return string
1248
+ */
1249
+ function fs_esc_html_inline( $text, $key = '', $slug = 'freemius' ) {
1250
+ return esc_html( _fs_text_inline( $text, $key, $slug ) );
1251
+ }
1252
+ }
1253
+
1254
+ if ( ! function_exists( 'fs_esc_html_x_inline' ) ) {
1255
+ /**
1256
+ * @author Vova Feldman (@svovaf)
1257
+ * @since 1.2.3
1258
+ *
1259
+ * @param string $text Translatable string.
1260
+ * @param string $context Context information for the translators.
1261
+ * @param string $key String key for overrides.
1262
+ * @param string $slug Module slug for overrides.
1263
+ *
1264
+ * @return string
1265
+ */
1266
+ function fs_esc_html_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1267
+ return esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) );
1268
+ }
1269
+ }
1270
+
1271
+ if ( ! function_exists( 'fs_esc_html_echo_x_inline' ) ) {
1272
+ /**
1273
+ * @author Vova Feldman (@svovaf)
1274
+ * @since 1.2.3
1275
+ *
1276
+ * @param string $text Translatable string.
1277
+ * @param string $context Context information for the translators.
1278
+ * @param string $key String key for overrides.
1279
+ * @param string $slug Module slug for overrides.
1280
+ */
1281
+ function fs_esc_html_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) {
1282
+ echo esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) );
1283
+ }
1284
+ }
1285
+
1286
+ if ( ! function_exists( 'fs_esc_html_echo' ) ) {
1287
+ /**
1288
+ * @author Vova Feldman
1289
+ * @since 1.2.1.6
1290
+ *
1291
+ * @param string $key
1292
+ * @param string $slug
1293
+ */
1294
+ function fs_esc_html_echo( $key, $slug ) {
1295
+ echo esc_html( fs_text( $key, $slug ) );
1296
+ }
1297
+ }
1298
+
1299
+ if ( ! function_exists( 'fs_esc_html_echo_inline' ) ) {
1300
+ /**
1301
+ * @author Vova Feldman (@svovaf)
1302
+ * @since 1.2.3
1303
+ *
1304
+ * @param string $text Translatable string.
1305
+ * @param string $key String key for overrides.
1306
+ * @param string $slug Module slug for overrides.
1307
+ */
1308
+ function fs_esc_html_echo_inline( $text, $key = '', $slug = 'freemius' ) {
1309
+ echo esc_html( _fs_text_inline( $text, $key, $slug ) );
1310
+ }
1311
+ }
1312
+
1313
+ if ( ! function_exists( 'fs_override_i18n' ) ) {
1314
+ /**
1315
+ * Override default i18n text phrases.
1316
+ *
1317
+ * @author Vova Feldman (@svovaf)
1318
+ * @since 1.1.6
1319
+ *
1320
+ * @param array[string]string $key_value
1321
+ * @param string $slug
1322
+ *
1323
+ * @global $fs_text_overrides
1324
+ */
1325
+ function fs_override_i18n( array $key_value, $slug = 'freemius' ) {
1326
+ global $fs_text_overrides;
1327
+
1328
+ if ( ! isset( $fs_text_overrides[ $slug ] ) ) {
1329
+ $fs_text_overrides[ $slug ] = array();
1330
+ }
1331
+
1332
+ foreach ( $key_value as $key => $value ) {
1333
+ $fs_text_overrides[ $slug ][ $key ] = $value;
1334
+ }
1335
+ }
1336
+ }
1337
+
1338
+ #endregion
1339
+
1340
+ #--------------------------------------------------------------------------------
1341
+ #region Multisite Network
1342
+ #--------------------------------------------------------------------------------
1343
+
1344
+ if ( ! function_exists( 'fs_is_plugin_uninstall' ) ) {
1345
+ /**
1346
+ * @author Vova Feldman (@svovaf)
1347
+ * @since 2.0.0
1348
+ */
1349
+ function fs_is_plugin_uninstall() {
1350
+ return (
1351
+ defined( 'WP_UNINSTALL_PLUGIN' ) ||
1352
+ ( 0 < did_action( 'update_option_uninstall_plugins' ) )
1353
+ );
1354
+ }
1355
+ }
1356
+
1357
+ if ( ! function_exists( 'fs_is_network_admin' ) ) {
1358
+ /**
1359
+ * Unlike is_network_admin(), this one will also work properly when
1360
+ * the context execution is WP AJAX handler, and during plugin
1361
+ * uninstall.
1362
+ *
1363
+ * @author Vova Feldman (@svovaf)
1364
+ * @since 2.0.0
1365
+ */
1366
+ function fs_is_network_admin() {
1367
+ return (
1368
+ WP_FS__IS_NETWORK_ADMIN ||
1369
+ ( is_multisite() && fs_is_plugin_uninstall() )
1370
+ );
1371
+ }
1372
+ }
1373
+
1374
+ if ( ! function_exists( 'fs_is_blog_admin' ) ) {
1375
+ /**
1376
+ * Unlike is_blog_admin(), this one will also work properly when
1377
+ * the context execution is WP AJAX handler, and during plugin
1378
+ * uninstall.
1379
+ *
1380
+ * @author Vova Feldman (@svovaf)
1381
+ * @since 2.0.0
1382
+ */
1383
+ function fs_is_blog_admin() {
1384
+ return (
1385
+ WP_FS__IS_BLOG_ADMIN ||
1386
+ ( ! is_multisite() && fs_is_plugin_uninstall() )
1387
+ );
1388
+ }
1389
+ }
1390
+
1391
+ #endregion
1392
+
1393
+ if ( ! function_exists( 'fs_apply_filter' ) ) {
1394
+ /**
1395
+ * Apply filter for specific plugin.
1396
+ *
1397
+ * @author Vova Feldman (@svovaf)
1398
+ * @since 1.0.9
1399
+ *
1400
+ * @param string $module_unique_affix Module's unique affix.
1401
+ * @param string $tag The name of the filter hook.
1402
+ * @param mixed $value The value on which the filters hooked to `$tag` are applied on.
1403
+ *
1404
+ * @return mixed The filtered value after all hooked functions are applied to it.
1405
+ *
1406
+ * @uses apply_filters()
1407
+ */
1408
+ function fs_apply_filter( $module_unique_affix, $tag, $value ) {
1409
+ $args = func_get_args();
1410
+
1411
+ return call_user_func_array( 'apply_filters', array_merge(
1412
+ array( "fs_{$tag}_{$module_unique_affix}" ),
1413
+ array_slice( $args, 2 ) )
1414
+ );
1415
+ }
1416
  }
freemius/includes/fs-essential-functions.php CHANGED
@@ -1,500 +1,500 @@
1
- <?php
2
- /**
3
- * IMPORTANT:
4
- * This file will be loaded based on the order of the plugins/themes load.
5
- * If there's a theme and a plugin using Freemius, the plugin's essential
6
- * file will always load first.
7
- *
8
- * @package Freemius
9
- * @copyright Copyright (c) 2015, Freemius, Inc.
10
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
11
- * @since 1.1.5
12
- */
13
-
14
- if ( ! function_exists( 'fs_normalize_path' ) ) {
15
- if ( function_exists( 'wp_normalize_path' ) ) {
16
- /**
17
- * Normalize a filesystem path.
18
- *
19
- * Replaces backslashes with forward slashes for Windows systems, and ensures
20
- * no duplicate slashes exist.
21
- *
22
- * @param string $path Path to normalize.
23
- *
24
- * @return string Normalized path.
25
- */
26
- function fs_normalize_path( $path ) {
27
- return wp_normalize_path( $path );
28
- }
29
- } else {
30
- function fs_normalize_path( $path ) {
31
- $path = str_replace( '\\', '/', $path );
32
- $path = preg_replace( '|/+|', '/', $path );
33
-
34
- return $path;
35
- }
36
- }
37
- }
38
-
39
- require_once dirname( __FILE__ ) . '/supplements/fs-essential-functions-2.2.1.php';
40
-
41
- #region Core Redirect (copied from BuddyPress) -----------------------------------------
42
-
43
- if ( ! function_exists( 'fs_redirect' ) ) {
44
- /**
45
- * Redirects to another page, with a workaround for the IIS Set-Cookie bug.
46
- *
47
- * @link http://support.microsoft.com/kb/q176113/
48
- * @since 1.5.1
49
- * @uses apply_filters() Calls 'wp_redirect' hook on $location and $status.
50
- *
51
- * @param string $location The path to redirect to.
52
- * @param bool $exit If true, exit after redirect (Since 1.2.1.5).
53
- * @param int $status Status code to use.
54
- *
55
- * @return bool False if $location is not set
56
- */
57
- function fs_redirect( $location, $exit = true, $status = 302 ) {
58
- global $is_IIS;
59
-
60
- $file = '';
61
- $line = '';
62
- if ( headers_sent($file, $line) ) {
63
- if ( WP_FS__DEBUG_SDK && class_exists( 'FS_Admin_Notices' ) ) {
64
- $notices = FS_Admin_Notices::instance( 'global' );
65
-
66
- $notices->add( "Freemius failed to redirect the page because the headers have been already sent from line <b><code>{$line}</code></b> in file <b><code>{$file}</code></b>. If it's unexpected, it usually happens due to invalid space and/or EOL character(s).", 'Oops...', 'error' );
67
- }
68
-
69
- return false;
70
- }
71
-
72
- if ( defined( 'DOING_AJAX' ) ) {
73
- // Don't redirect on AJAX calls.
74
- return false;
75
- }
76
-
77
- if ( ! $location ) // allows the wp_redirect filter to cancel a redirect
78
- {
79
- return false;
80
- }
81
-
82
- $location = fs_sanitize_redirect( $location );
83
-
84
- if ( $is_IIS ) {
85
- header( "Refresh: 0;url=$location" );
86
- } else {
87
- if ( php_sapi_name() != 'cgi-fcgi' ) {
88
- status_header( $status );
89
- } // This causes problems on IIS and some FastCGI setups
90
- header( "Location: $location" );
91
- }
92
-
93
- if ( $exit ) {
94
- exit();
95
- }
96
-
97
- return true;
98
- }
99
-
100
- if ( ! function_exists( 'fs_sanitize_redirect' ) ) {
101
- /**
102
- * Sanitizes a URL for use in a redirect.
103
- *
104
- * @since 2.3
105
- *
106
- * @param string $location
107
- *
108
- * @return string redirect-sanitized URL
109
- */
110
- function fs_sanitize_redirect( $location ) {
111
- $location = preg_replace( '|[^a-z0-9-~+_.?#=&;,/:%!]|i', '', $location );
112
- $location = fs_kses_no_null( $location );
113
-
114
- // remove %0d and %0a from location
115
- $strip = array( '%0d', '%0a' );
116
- $found = true;
117
- while ( $found ) {
118
- $found = false;
119
- foreach ( (array) $strip as $val ) {
120
- while ( strpos( $location, $val ) !== false ) {
121
- $found = true;
122
- $location = str_replace( $val, '', $location );
123
- }
124
- }
125
- }
126
-
127
- return $location;
128
- }
129
- }
130
-
131
- if ( ! function_exists( 'fs_kses_no_null' ) ) {
132
- /**
133
- * Removes any NULL characters in $string.
134
- *
135
- * @since 1.0.0
136
- *
137
- * @param string $string
138
- *
139
- * @return string
140
- */
141
- function fs_kses_no_null( $string ) {
142
- $string = preg_replace( '/\0+/', '', $string );
143
- $string = preg_replace( '/(\\\\0)+/', '', $string );
144
-
145
- return $string;
146
- }
147
- }
148
- }
149
-
150
- #endregion Core Redirect (copied from BuddyPress) -----------------------------------------
151
-
152
- if ( ! function_exists( '__fs' ) ) {
153
- global $fs_text_overrides;
154
-
155
- if ( ! isset( $fs_text_overrides ) ) {
156
- $fs_text_overrides = array();
157
- }
158
-
159
- /**
160
- * Retrieve a translated text by key.
161
- *
162
- * @deprecated Use `fs_text()` instead since methods starting with `__` trigger warnings in Php 7.
163
- * @todo Remove this method in the future.
164
- *
165
- * @author Vova Feldman (@svovaf)
166
- * @since 1.1.4
167
- *
168
- * @param string $key
169
- * @param string $slug
170
- *
171
- * @return string
172
- *
173
- * @global $fs_text, $fs_text_overrides
174
- */
175
- function __fs( $key, $slug = 'freemius' ) {
176
- _deprecated_function( __FUNCTION__, '2.0.0', 'fs_text()' );
177
-
178
- global $fs_text,
179
- $fs_module_info_text,
180
- $fs_text_overrides;
181
-
182
- if ( isset( $fs_text_overrides[ $slug ] ) ) {
183
- if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) {
184
- return $fs_text_overrides[ $slug ][ $key ];
185
- }
186
-
187
- $lower_key = strtolower( $key );
188
- if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) {
189
- return $fs_text_overrides[ $slug ][ $lower_key ];
190
- }
191
- }
192
-
193
- if ( ! isset( $fs_text ) ) {
194
- $dir = defined( 'WP_FS__DIR_INCLUDES' ) ?
195
- WP_FS__DIR_INCLUDES :
196
- dirname( __FILE__ );
197
-
198
- require_once $dir . '/i18n.php';
199
- }
200
-
201
- if ( isset( $fs_text[ $key ] ) ) {
202
- return $fs_text[ $key ];
203
- }
204
-
205
- if ( isset( $fs_module_info_text[ $key ] ) ) {
206
- return $fs_module_info_text[ $key ];
207
- }
208
-
209
- return $key;
210
- }
211
-
212
- /**
213
- * Output a translated text by key.
214
- *
215
- * @deprecated Use `fs_echo()` instead for consistency with `fs_text()`.
216
- *
217
- * @todo Remove this method in the future.
218
- *
219
- * @author Vova Feldman (@svovaf)
220
- * @since 1.1.4
221
- *
222
- * @param string $key
223
- * @param string $slug
224
- */
225
- function _efs( $key, $slug = 'freemius' ) {
226
- fs_echo( $key, $slug );
227
- }
228
- }
229
-
230
- if ( ! function_exists( 'fs_get_ip' ) ) {
231
- /**
232
- * Get client IP.
233
- *
234
- * @author Vova Feldman (@svovaf)
235
- * @since 1.1.2
236
- *
237
- * @return string|null
238
- */
239
- function fs_get_ip() {
240
- $fields = array(
241
- 'HTTP_CF_CONNECTING_IP',
242
- 'HTTP_CLIENT_IP',
243
- 'HTTP_X_FORWARDED_FOR',
244
- 'HTTP_X_FORWARDED',
245
- 'HTTP_FORWARDED_FOR',
246
- 'HTTP_FORWARDED',
247
- 'REMOTE_ADDR',
248
- );
249
-
250
- foreach ( $fields as $ip_field ) {
251
- if ( ! empty( $_SERVER[ $ip_field ] ) ) {
252
- return $_SERVER[ $ip_field ];
253
- }
254
- }
255
-
256
- return null;
257
- }
258
- }
259
-
260
- /**
261
- * Leverage backtrace to find caller plugin main file path.
262
- *
263
- * @author Vova Feldman (@svovaf)
264
- * @since 1.0.6
265
- *
266
- * @return string
267
- */
268
- function fs_find_caller_plugin_file() {
269
- /**
270
- * All the code below will be executed once on activation.
271
- * If the user changes the main plugin's file name, the file_exists()
272
- * will catch it.
273
- */
274
- if ( ! function_exists( 'get_plugins' ) ) {
275
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
276
- }
277
-
278
- $all_plugins = fs_get_plugins( true );
279
- $all_plugins_paths = array();
280
-
281
- // Get active plugin's main files real full names (might be symlinks).
282
- foreach ( $all_plugins as $relative_path => $data ) {
283
- $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) );
284
- }
285
-
286
- $plugin_file = null;
287
- for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) {
288
- if ( empty( $bt[ $i ]['file'] ) ) {
289
- continue;
290
- }
291
-
292
- if ( in_array( fs_normalize_path( $bt[ $i ]['file'] ), $all_plugins_paths ) ) {
293
- $plugin_file = $bt[ $i ]['file'];
294
- break;
295
- }
296
- }
297
-
298
- if ( is_null( $plugin_file ) ) {
299
- // Throw an error to the developer in case of some edge case dev environment.
300
- wp_die(
301
- 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.',
302
- 'Error',
303
- array( 'back_link' => true )
304
- );
305
- }
306
-
307
- return $plugin_file;
308
- }
309
-
310
- require_once dirname( __FILE__ ) . '/supplements/fs-essential-functions-1.1.7.1.php';
311
-
312
- /**
313
- * Update SDK newest version reference.
314
- *
315
- * @author Vova Feldman (@svovaf)
316
- * @since 1.1.6
317
- *
318
- * @param string $sdk_relative_path
319
- * @param string|bool $plugin_file
320
- *
321
- * @global $fs_active_plugins
322
- */
323
- function fs_update_sdk_newest_version( $sdk_relative_path, $plugin_file = false ) {
324
- /**
325
- * If there is a plugin running an older version of FS (1.2.1 or below), the `fs_update_sdk_newest_version()`
326
- * function in the older version will be used instead of this one. But since the older version is using
327
- * the `is_plugin_active` function to check if a plugin is active, passing the theme's `plugin_path` to the
328
- * `is_plugin_active` function will return false since the path is not a plugin path, so `in_activation` will be
329
- * `true` for theme modules and the upgrading of the SDK version to 1.2.2 or newer version will work fine.
330
- *
331
- * Future versions that will call this function will use the proper logic here instead of just relying on the
332
- * `is_plugin_active` function to fail for themes.
333
- *
334
- * @author Leo Fajardo (@leorw)
335
- * @since 1.2.2
336
- */
337
-
338
- global $fs_active_plugins;
339
-
340
- $newest_sdk = $fs_active_plugins->plugins[ $sdk_relative_path ];
341
-
342
- if ( ! is_string( $plugin_file ) ) {
343
- $plugin_file = plugin_basename( fs_find_caller_plugin_file() );
344
- }
345
-
346
- if ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) {
347
- if ( ! function_exists( 'is_plugin_active' ) ) {
348
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
349
- }
350
-
351
- $in_activation = ( ! is_plugin_active( $plugin_file ) );
352
- } else {
353
- $theme = wp_get_theme();
354
- $in_activation = ( $newest_sdk->plugin_path == $theme->stylesheet );
355
- }
356
-
357
- $fs_active_plugins->newest = (object) array(
358
- 'plugin_path' => $plugin_file,
359
- 'sdk_path' => $sdk_relative_path,
360
- 'version' => $newest_sdk->version,
361
- 'in_activation' => $in_activation,
362
- 'timestamp' => time(),
363
- );
364
-
365
- // Update DB with latest SDK version and path.
366
- update_option( 'fs_active_plugins', $fs_active_plugins );
367
- }
368
-
369
- /**
370
- * Reorder the plugins load order so the plugin with the newest Freemius SDK is loaded first.
371
- *
372
- * @author Vova Feldman (@svovaf)
373
- * @since 1.1.6
374
- *
375
- * @return bool Was plugin order changed. Return false if plugin was loaded first anyways.
376
- *
377
- * @global $fs_active_plugins
378
- */
379
- function fs_newest_sdk_plugin_first() {
380
- global $fs_active_plugins;
381
-
382
- /**
383
- * @todo Multi-site network activated plugin are always loaded prior to site plugins so if there's a plugin activated in the network mode that has an older version of the SDK of another plugin which is site activated that has new SDK version, the fs-essential-functions.php will be loaded from the older SDK. Same thing about MU plugins (loaded even before network activated plugins).
384
- *
385
- * @link https://github.com/Freemius/wordpress-sdk/issues/26
386
- */
387
-
388
- $newest_sdk_plugin_path = $fs_active_plugins->newest->plugin_path;
389
-
390
- $active_plugins = get_option( 'active_plugins', array() );
391
- $updated_active_plugins = array( $newest_sdk_plugin_path );
392
-
393
- $plugin_found = false;
394
- $is_first_path = true;
395
-
396
- foreach ( $active_plugins as $key => $plugin_path ) {
397
- if ( $plugin_path === $newest_sdk_plugin_path ) {
398
- if ( $is_first_path ) {
399
- // if it's the first plugin already, no need to continue
400
- return false;
401
- }
402
-
403
- $plugin_found = true;
404
-
405
- // Skip the plugin (it is already added as the 1st item of $updated_active_plugins).
406
- continue;
407
- }
408
-
409
- $updated_active_plugins[] = $plugin_path;
410
-
411
- if ( $is_first_path ) {
412
- $is_first_path = false;
413
- }
414
- }
415
-
416
- if ( $plugin_found ) {
417
- update_option( 'active_plugins', $updated_active_plugins );
418
-
419
- return true;
420
- }
421
-
422
- if ( is_multisite() ) {
423
- // Plugin is network active.
424
- $network_active_plugins = get_site_option( 'active_sitewide_plugins', array() );
425
-
426
- if ( isset( $network_active_plugins[ $newest_sdk_plugin_path ] ) ) {
427
- reset( $network_active_plugins );
428
- if ( $newest_sdk_plugin_path === key( $network_active_plugins ) ) {
429
- // Plugin is already activated first on the network level.
430
- return false;
431
- } else {
432
- $time = $network_active_plugins[ $newest_sdk_plugin_path ];
433
-
434
- // Remove plugin from its current position.
435
- unset( $network_active_plugins[ $newest_sdk_plugin_path ] );
436
-
437
- // Set it to be included first.
438
- $network_active_plugins = array( $newest_sdk_plugin_path => $time ) + $network_active_plugins;
439
-
440
- update_site_option( 'active_sitewide_plugins', $network_active_plugins );
441
-
442
- return true;
443
- }
444
- }
445
- }
446
-
447
- return false;
448
- }
449
-
450
- /**
451
- * Go over all Freemius SDKs in the system and find and "remember"
452
- * the newest SDK which is associated with an active plugin.
453
- *
454
- * @author Vova Feldman (@svovaf)
455
- * @since 1.1.6
456
- *
457
- * @global $fs_active_plugins
458
- */
459
- function fs_fallback_to_newest_active_sdk() {
460
- global $fs_active_plugins;
461
-
462
- /**
463
- * @var object $newest_sdk_data
464
- */
465
- $newest_sdk_data = null;
466
- $newest_sdk_path = null;
467
-
468
- foreach ( $fs_active_plugins->plugins as $sdk_relative_path => $data ) {
469
- if ( is_null( $newest_sdk_data ) || version_compare( $data->version, $newest_sdk_data->version, '>' )
470
- ) {
471
- // If plugin inactive or SDK starter file doesn't exist, remove SDK reference.
472
- if ( 'plugin' === $data->type ) {
473
- $is_module_active = is_plugin_active( $data->plugin_path );
474
- } else {
475
- $active_theme = wp_get_theme();
476
- $is_module_active = ( $data->plugin_path === $active_theme->get_template() );
477
- }
478
-
479
- $is_sdk_exists = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $sdk_relative_path . '/start.php' ) );
480
-
481
- if ( ! $is_module_active || ! $is_sdk_exists ) {
482
- unset( $fs_active_plugins->plugins[ $sdk_relative_path ] );
483
-
484
- // No need to store the data since it will be stored in fs_update_sdk_newest_version()
485
- // or explicitly with update_option().
486
- } else {
487
- $newest_sdk_data = $data;
488
- $newest_sdk_path = $sdk_relative_path;
489
- }
490
- }
491
- }
492
-
493
- if ( is_null( $newest_sdk_data ) ) {
494
- // Couldn't find any SDK reference.
495
- $fs_active_plugins = new stdClass();
496
- update_option( 'fs_active_plugins', $fs_active_plugins );
497
- } else {
498
- fs_update_sdk_newest_version( $newest_sdk_path, $newest_sdk_data->plugin_path );
499
- }
500
  }
1
+ <?php
2
+ /**
3
+ * IMPORTANT:
4
+ * This file will be loaded based on the order of the plugins/themes load.
5
+ * If there's a theme and a plugin using Freemius, the plugin's essential
6
+ * file will always load first.
7
+ *
8
+ * @package Freemius
9
+ * @copyright Copyright (c) 2015, Freemius, Inc.
10
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
11
+ * @since 1.1.5
12
+ */
13
+
14
+ if ( ! function_exists( 'fs_normalize_path' ) ) {
15
+ if ( function_exists( 'wp_normalize_path' ) ) {
16
+ /**
17
+ * Normalize a filesystem path.
18
+ *
19
+ * Replaces backslashes with forward slashes for Windows systems, and ensures
20
+ * no duplicate slashes exist.
21
+ *
22
+ * @param string $path Path to normalize.
23
+ *
24
+ * @return string Normalized path.
25
+ */
26
+ function fs_normalize_path( $path ) {
27
+ return wp_normalize_path( $path );
28
+ }
29
+ } else {
30
+ function fs_normalize_path( $path ) {
31
+ $path = str_replace( '\\', '/', $path );
32
+ $path = preg_replace( '|/+|', '/', $path );
33
+
34
+ return $path;
35
+ }
36
+ }
37
+ }
38
+
39
+ require_once dirname( __FILE__ ) . '/supplements/fs-essential-functions-2.2.1.php';
40
+
41
+ #region Core Redirect (copied from BuddyPress) -----------------------------------------
42
+
43
+ if ( ! function_exists( 'fs_redirect' ) ) {
44
+ /**
45
+ * Redirects to another page, with a workaround for the IIS Set-Cookie bug.
46
+ *
47
+ * @link http://support.microsoft.com/kb/q176113/
48
+ * @since 1.5.1
49
+ * @uses apply_filters() Calls 'wp_redirect' hook on $location and $status.
50
+ *
51
+ * @param string $location The path to redirect to.
52
+ * @param bool $exit If true, exit after redirect (Since 1.2.1.5).
53
+ * @param int $status Status code to use.
54
+ *
55
+ * @return bool False if $location is not set
56
+ */
57
+ function fs_redirect( $location, $exit = true, $status = 302 ) {
58
+ global $is_IIS;
59
+
60
+ $file = '';
61
+ $line = '';
62
+ if ( headers_sent($file, $line) ) {
63
+ if ( WP_FS__DEBUG_SDK && class_exists( 'FS_Admin_Notices' ) ) {
64
+ $notices = FS_Admin_Notices::instance( 'global' );
65
+
66
+ $notices->add( "Freemius failed to redirect the page because the headers have been already sent from line <b><code>{$line}</code></b> in file <b><code>{$file}</code></b>. If it's unexpected, it usually happens due to invalid space and/or EOL character(s).", 'Oops...', 'error' );
67
+ }
68
+
69
+ return false;
70
+ }
71
+
72
+ if ( defined( 'DOING_AJAX' ) ) {
73
+ // Don't redirect on AJAX calls.
74
+ return false;
75
+ }
76
+
77
+ if ( ! $location ) // allows the wp_redirect filter to cancel a redirect
78
+ {
79
+ return false;
80
+ }
81
+
82
+ $location = fs_sanitize_redirect( $location );
83
+
84
+ if ( $is_IIS ) {
85
+ header( "Refresh: 0;url=$location" );
86
+ } else {
87
+ if ( php_sapi_name() != 'cgi-fcgi' ) {
88
+ status_header( $status );
89
+ } // This causes problems on IIS and some FastCGI setups
90
+ header( "Location: $location" );
91
+ }
92
+
93
+ if ( $exit ) {
94
+ exit();
95
+ }
96
+
97
+ return true;
98
+ }
99
+
100
+ if ( ! function_exists( 'fs_sanitize_redirect' ) ) {
101
+ /**
102
+ * Sanitizes a URL for use in a redirect.
103
+ *
104
+ * @since 2.3
105
+ *
106
+ * @param string $location
107
+ *
108
+ * @return string redirect-sanitized URL
109
+ */
110
+ function fs_sanitize_redirect( $location ) {
111
+ $location = preg_replace( '|[^a-z0-9-~+_.?#=&;,/:%!]|i', '', $location );
112
+ $location = fs_kses_no_null( $location );
113
+
114
+ // remove %0d and %0a from location
115
+ $strip = array( '%0d', '%0a' );
116
+ $found = true;
117
+ while ( $found ) {
118
+ $found = false;
119
+ foreach ( (array) $strip as $val ) {
120
+ while ( strpos( $location, $val ) !== false ) {
121
+ $found = true;
122
+ $location = str_replace( $val, '', $location );
123
+ }
124
+ }
125
+ }
126
+
127
+ return $location;
128
+ }
129
+ }
130
+
131
+ if ( ! function_exists( 'fs_kses_no_null' ) ) {
132
+ /**
133
+ * Removes any NULL characters in $string.
134
+ *
135
+ * @since 1.0.0
136
+ *
137
+ * @param string $string
138
+ *
139
+ * @return string
140
+ */
141
+ function fs_kses_no_null( $string ) {
142
+ $string = preg_replace( '/\0+/', '', $string );
143
+ $string = preg_replace( '/(\\\\0)+/', '', $string );
144
+
145
+ return $string;
146
+ }
147
+ }
148
+ }
149
+
150
+ #endregion Core Redirect (copied from BuddyPress) -----------------------------------------
151
+
152
+ if ( ! function_exists( '__fs' ) ) {
153
+ global $fs_text_overrides;
154
+
155
+ if ( ! isset( $fs_text_overrides ) ) {
156
+ $fs_text_overrides = array();
157
+ }
158
+
159
+ /**
160
+ * Retrieve a translated text by key.
161
+ *
162
+ * @deprecated Use `fs_text()` instead since methods starting with `__` trigger warnings in Php 7.
163
+ * @todo Remove this method in the future.
164
+ *
165
+ * @author Vova Feldman (@svovaf)
166
+ * @since 1.1.4
167
+ *
168
+ * @param string $key
169
+ * @param string $slug
170
+ *
171
+ * @return string
172
+ *
173
+ * @global $fs_text, $fs_text_overrides
174
+ */
175
+ function __fs( $key, $slug = 'freemius' ) {
176
+ _deprecated_function( __FUNCTION__, '2.0.0', 'fs_text()' );
177
+
178
+ global $fs_text,
179
+ $fs_module_info_text,
180
+ $fs_text_overrides;
181
+
182
+ if ( isset( $fs_text_overrides[ $slug ] ) ) {
183
+ if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) {
184
+ return $fs_text_overrides[ $slug ][ $key ];
185
+ }
186
+
187
+ $lower_key = strtolower( $key );
188
+ if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) {
189
+ return $fs_text_overrides[ $slug ][ $lower_key ];
190
+ }
191
+ }
192
+
193
+ if ( ! isset( $fs_text ) ) {
194
+ $dir = defined( 'WP_FS__DIR_INCLUDES' ) ?
195
+ WP_FS__DIR_INCLUDES :
196
+ dirname( __FILE__ );
197
+
198
+ require_once $dir . '/i18n.php';
199
+ }
200
+
201
+ if ( isset( $fs_text[ $key ] ) ) {
202
+ return $fs_text[ $key ];
203
+ }
204
+
205
+ if ( isset( $fs_module_info_text[ $key ] ) ) {
206
+ return $fs_module_info_text[ $key ];
207
+ }
208
+
209
+ return $key;
210
+ }
211
+
212
+ /**
213
+ * Output a translated text by key.
214
+ *
215
+ * @deprecated Use `fs_echo()` instead for consistency with `fs_text()`.
216
+ *
217
+ * @todo Remove this method in the future.
218
+ *
219
+ * @author Vova Feldman (@svovaf)
220
+ * @since 1.1.4
221
+ *
222
+ * @param string $key
223
+ * @param string $slug
224
+ */
225
+ function _efs( $key, $slug = 'freemius' ) {
226
+ fs_echo( $key, $slug );
227
+ }
228
+ }
229
+
230
+ if ( ! function_exists( 'fs_get_ip' ) ) {
231
+ /**
232
+ * Get client IP.
233
+ *
234
+ * @author Vova Feldman (@svovaf)
235
+ * @since 1.1.2
236
+ *
237
+ * @return string|null
238
+ */
239
+ function fs_get_ip() {
240
+ $fields = array(
241
+ 'HTTP_CF_CONNECTING_IP',
242
+ 'HTTP_CLIENT_IP',
243
+ 'HTTP_X_FORWARDED_FOR',
244
+ 'HTTP_X_FORWARDED',
245
+ 'HTTP_FORWARDED_FOR',
246
+ 'HTTP_FORWARDED',
247
+ 'REMOTE_ADDR',
248
+ );
249
+
250
+ foreach ( $fields as $ip_field ) {
251
+ if ( ! empty( $_SERVER[ $ip_field ] ) ) {
252
+ return $_SERVER[ $ip_field ];
253
+ }
254
+ }
255
+
256
+ return null;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Leverage backtrace to find caller plugin main file path.
262
+ *
263
+ * @author Vova Feldman (@svovaf)
264
+ * @since 1.0.6
265
+ *
266
+ * @return string
267
+ */
268
+ function fs_find_caller_plugin_file() {
269
+ /**
270
+ * All the code below will be executed once on activation.
271
+ * If the user changes the main plugin's file name, the file_exists()
272
+ * will catch it.
273
+ */
274
+ if ( ! function_exists( 'get_plugins' ) ) {
275
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
276
+ }
277
+
278
+ $all_plugins = fs_get_plugins( true );
279
+ $all_plugins_paths = array();
280
+
281
+ // Get active plugin's main files real full names (might be symlinks).
282
+ foreach ( $all_plugins as $relative_path => $data ) {
283
+ $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) );
284
+ }
285
+
286
+ $plugin_file = null;
287
+ for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) {
288
+ if ( empty( $bt[ $i ]['file'] ) ) {
289
+ continue;
290
+ }
291
+
292
+ if ( in_array( fs_normalize_path( $bt[ $i ]['file'] ), $all_plugins_paths ) ) {
293
+ $plugin_file = $bt[ $i ]['file'];
294
+ break;
295
+ }
296
+ }
297
+
298
+ if ( is_null( $plugin_file ) ) {
299
+ // Throw an error to the developer in case of some edge case dev environment.
300
+ wp_die(
301
+ 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.',
302
+ 'Error',
303
+ array( 'back_link' => true )
304
+ );
305
+ }
306
+
307
+ return $plugin_file;
308
+ }
309
+
310
+ require_once dirname( __FILE__ ) . '/supplements/fs-essential-functions-1.1.7.1.php';
311
+
312
+ /**
313
+ * Update SDK newest version reference.
314
+ *
315
+ * @author Vova Feldman (@svovaf)
316
+ * @since 1.1.6
317
+ *
318
+ * @param string $sdk_relative_path
319
+ * @param string|bool $plugin_file
320
+ *
321
+ * @global $fs_active_plugins
322
+ */
323
+ function fs_update_sdk_newest_version( $sdk_relative_path, $plugin_file = false ) {
324
+ /**
325
+ * If there is a plugin running an older version of FS (1.2.1 or below), the `fs_update_sdk_newest_version()`
326
+ * function in the older version will be used instead of this one. But since the older version is using
327
+ * the `is_plugin_active` function to check if a plugin is active, passing the theme's `plugin_path` to the
328
+ * `is_plugin_active` function will return false since the path is not a plugin path, so `in_activation` will be
329
+ * `true` for theme modules and the upgrading of the SDK version to 1.2.2 or newer version will work fine.
330
+ *
331
+ * Future versions that will call this function will use the proper logic here instead of just relying on the
332
+ * `is_plugin_active` function to fail for themes.
333
+ *
334
+ * @author Leo Fajardo (@leorw)
335
+ * @since 1.2.2
336
+ */
337
+
338
+ global $fs_active_plugins;
339
+
340
+ $newest_sdk = $fs_active_plugins->plugins[ $sdk_relative_path ];
341
+
342
+ if ( ! is_string( $plugin_file ) ) {
343
+ $plugin_file = plugin_basename( fs_find_caller_plugin_file() );
344
+ }
345
+
346
+ if ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) {
347
+ if ( ! function_exists( 'is_plugin_active' ) ) {
348
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
349
+ }
350
+
351
+ $in_activation = ( ! is_plugin_active( $plugin_file ) );
352
+ } else {
353
+ $theme = wp_get_theme();
354
+ $in_activation = ( $newest_sdk->plugin_path == $theme->stylesheet );
355
+ }
356
+
357
+ $fs_active_plugins->newest = (object) array(
358
+ 'plugin_path' => $plugin_file,
359
+ 'sdk_path' => $sdk_relative_path,
360
+ 'version' => $newest_sdk->version,
361
+ 'in_activation' => $in_activation,
362
+ 'timestamp' => time(),
363
+ );
364
+
365
+ // Update DB with latest SDK version and path.
366
+ update_option( 'fs_active_plugins', $fs_active_plugins );
367
+ }
368
+
369
+ /**
370
+ * Reorder the plugins load order so the plugin with the newest Freemius SDK is loaded first.
371
+ *
372
+ * @author Vova Feldman (@svovaf)
373
+ * @since 1.1.6
374
+ *
375
+ * @return bool Was plugin order changed. Return false if plugin was loaded first anyways.
376
+ *
377
+ * @global $fs_active_plugins
378
+ */
379
+ function fs_newest_sdk_plugin_first() {
380
+ global $fs_active_plugins;
381
+
382
+ /**
383
+ * @todo Multi-site network activated plugin are always loaded prior to site plugins so if there's a plugin activated in the network mode that has an older version of the SDK of another plugin which is site activated that has new SDK version, the fs-essential-functions.php will be loaded from the older SDK. Same thing about MU plugins (loaded even before network activated plugins).
384
+ *
385
+ * @link https://github.com/Freemius/wordpress-sdk/issues/26
386
+ */
387
+
388
+ $newest_sdk_plugin_path = $fs_active_plugins->newest->plugin_path;
389
+
390
+ $active_plugins = get_option( 'active_plugins', array() );
391
+ $updated_active_plugins = array( $newest_sdk_plugin_path );
392
+
393
+ $plugin_found = false;
394
+ $is_first_path = true;
395
+
396
+ foreach ( $active_plugins as $key => $plugin_path ) {
397
+ if ( $plugin_path === $newest_sdk_plugin_path ) {
398
+ if ( $is_first_path ) {
399
+ // if it's the first plugin already, no need to continue
400
+ return false;
401
+ }
402
+
403
+ $plugin_found = true;
404
+
405
+ // Skip the plugin (it is already added as the 1st item of $updated_active_plugins).
406
+ continue;
407
+ }
408
+
409
+ $updated_active_plugins[] = $plugin_path;
410
+
411
+ if ( $is_first_path ) {
412
+ $is_first_path = false;
413
+ }
414
+ }
415
+
416
+ if ( $plugin_found ) {
417
+ update_option( 'active_plugins', $updated_active_plugins );
418
+
419
+ return true;
420
+ }
421
+
422
+ if ( is_multisite() ) {
423
+ // Plugin is network active.
424
+ $network_active_plugins = get_site_option( 'active_sitewide_plugins', array() );
425
+
426
+ if ( isset( $network_active_plugins[ $newest_sdk_plugin_path ] ) ) {
427
+ reset( $network_active_plugins );
428
+ if ( $newest_sdk_plugin_path === key( $network_active_plugins ) ) {
429
+ // Plugin is already activated first on the network level.
430
+ return false;
431
+ } else {
432
+ $time = $network_active_plugins[ $newest_sdk_plugin_path ];
433
+
434
+ // Remove plugin from its current position.
435
+ unset( $network_active_plugins[ $newest_sdk_plugin_path ] );
436
+
437
+ // Set it to be included first.
438
+ $network_active_plugins = array( $newest_sdk_plugin_path => $time ) + $network_active_plugins;
439
+
440
+ update_site_option( 'active_sitewide_plugins', $network_active_plugins );
441
+
442
+ return true;
443
+ }
444
+ }
445
+ }
446
+
447
+ return false;
448
+ }
449
+
450
+ /**
451
+ * Go over all Freemius SDKs in the system and find and "remember"
452
+ * the newest SDK which is associated with an active plugin.
453
+ *
454
+ * @author Vova Feldman (@svovaf)
455
+ * @since 1.1.6
456
+ *
457
+ * @global $fs_active_plugins
458
+ */
459
+ function fs_fallback_to_newest_active_sdk() {
460
+ global $fs_active_plugins;
461
+
462
+ /**
463
+ * @var object $newest_sdk_data
464
+ */
465
+ $newest_sdk_data = null;
466
+ $newest_sdk_path = null;
467
+
468
+ foreach ( $fs_active_plugins->plugins as $sdk_relative_path => $data ) {
469
+ if ( is_null( $newest_sdk_data ) || version_compare( $data->version, $newest_sdk_data->version, '>' )
470
+ ) {
471
+ // If plugin inactive or SDK starter file doesn't exist, remove SDK reference.
472
+ if ( 'plugin' === $data->type ) {
473
+ $is_module_active = is_plugin_active( $data->plugin_path );
474
+ } else {
475
+ $active_theme = wp_get_theme();
476
+ $is_module_active = ( $data->plugin_path === $active_theme->get_template() );
477
+ }
478
+
479
+ $is_sdk_exists = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $sdk_relative_path . '/start.php' ) );
480
+
481
+ if ( ! $is_module_active || ! $is_sdk_exists ) {
482
+ unset( $fs_active_plugins->plugins[ $sdk_relative_path ] );
483
+
484
+ // No need to store the data since it will be stored in fs_update_sdk_newest_version()
485
+ // or explicitly with update_option().
486
+ } else {
487
+ $newest_sdk_data = $data;
488
+ $newest_sdk_path = $sdk_relative_path;
489
+ }
490
+ }
491
+ }
492
+
493
+ if ( is_null( $newest_sdk_data ) ) {
494
+ // Couldn't find any SDK reference.
495
+ $fs_active_plugins = new stdClass();
496
+ update_option( 'fs_active_plugins', $fs_active_plugins );
497
+ } else {
498
+ fs_update_sdk_newest_version( $newest_sdk_path, $newest_sdk_data->plugin_path );
499
+ }
500
  }
freemius/includes/fs-plugin-info-dialog.php CHANGED
@@ -1,1644 +1,1644 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.0.6
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * Class FS_Plugin_Info_Dialog
15
- *
16
- * @author Vova Feldman (@svovaf)
17
- * @since 1.1.7
18
- */
19
- class FS_Plugin_Info_Dialog {
20
- /**
21
- * @since 1.1.7
22
- *
23
- * @var FS_Logger
24
- */
25
- private $_logger;
26
-
27
- /**
28
- * @since 1.1.7
29
- *
30
- * @var Freemius
31
- */
32
- private $_fs;
33
-
34
- /**
35
- * Collection of plugin installation, update, download, activation, and purchase actions. This is used in
36
- * populating the actions dropdown list when there are at least 2 actions. If there's only 1 action, a button
37
- * is used instead.
38
- *
39
- * @author Leo Fajardo (@leorw)
40
- * @since 2.3.0
41
- *
42
- * @var string[]
43
- */
44
- private $actions;
45
-
46
- /**
47
- * Contains plugin status information that is used to determine which actions should be part of the actions
48
- * dropdown list.
49
- *
50
- * @author Leo Fajardo (@leorw)
51
- * @since 2.3.0
52
- *
53
- * @var string[]
54
- */
55
- private $status;
56
-
57
- function __construct( Freemius $fs ) {
58
- $this->_fs = $fs;
59
-
60
- $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
61
-
62
- // Remove default plugin information action.
63
- remove_all_actions( 'install_plugins_pre_plugin-information' );
64
-
65
- // Override action with custom plugins function for add-ons.
66
- add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) );
67
-
68
- // Override request for plugin information for Add-ons.
69
- add_filter(
70
- 'fs_plugins_api',
71
- array( &$this, '_get_addon_info_filter' ),
72
- WP_FS__DEFAULT_PRIORITY, 3 );
73
- }
74
-
75
- /**
76
- * Generate add-on plugin information.
77
- *
78
- * @author Vova Feldman (@svovaf)
79
- * @since 1.0.6
80
- *
81
- * @param array $data
82
- * @param string $action
83
- * @param object|null $args
84
- *
85
- * @return array|null
86
- */
87
- function _get_addon_info_filter( $data, $action = '', $args = null ) {
88
- $this->_logger->entrance();
89
-
90
- $parent_plugin_id = fs_request_get( 'parent_plugin_id', $this->_fs->get_id() );
91
-
92
- if ( $this->_fs->get_id() != $parent_plugin_id ||
93
- ( 'plugin_information' !== $action ) ||
94
- ! isset( $args->slug )
95
- ) {
96
- return $data;
97
- }
98
-
99
- // Find add-on by slug.
100
- $selected_addon = $this->_fs->get_addon_by_slug( $args->slug, WP_FS__DEV_MODE );
101
-
102
- if ( false === $selected_addon ) {
103
- return $data;
104
- }
105
-
106
- if ( ! isset( $selected_addon->info ) ) {
107
- // Setup some default info.
108
- $selected_addon->info = new stdClass();
109
- $selected_addon->info->selling_point_0 = 'Selling Point 1';
110
- $selected_addon->info->selling_point_1 = 'Selling Point 2';
111
- $selected_addon->info->selling_point_2 = 'Selling Point 3';
112
- $selected_addon->info->description = '<p>Tell your users all about your add-on</p>';
113
- }
114
-
115
- fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
116
-
117
- $data = $args;
118
-
119
- $has_free_plan = false;
120
- $has_paid_plan = false;
121
-
122
- // Load add-on pricing.
123
- $has_pricing = false;
124
- $has_features = false;
125
- $plans = false;
126
-
127
- $result = $this->_fs->get_api_plugin_scope()->get( $this->_fs->add_show_pending( "/addons/{$selected_addon->id}/pricing.json?type=visible" ) );
128
-
129
- if ( ! isset( $result->error ) ) {
130
- $plans = $result->plans;
131
-
132
- if ( is_array( $plans ) ) {
133
- for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) {
134
- $pricing = isset( $plans[ $i ]->pricing ) ? $plans[ $i ]->pricing : null;
135
- $features = isset( $plans[ $i ]->features ) ? $plans[ $i ]->features : null;
136
-
137
- $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] );
138
- $plan = $plans[ $i ];
139
-
140
- if ( 'free' == $plans[ $i ]->name ||
141
- ! is_array( $pricing ) ||
142
- 0 == count( $pricing )
143
- ) {
144
- $has_free_plan = true;
145
- }
146
-
147
- if ( is_array( $pricing ) && 0 < count( $pricing ) ) {
148
- $filtered_pricing = array();
149
-
150
- foreach ( $pricing as $prices ) {
151
- $prices = new FS_Pricing( $prices );
152
-
153
- if ( ! $prices->is_usd() ) {
154
- /**
155
- * Skip non-USD pricing.
156
- *
157
- * @author Leo Fajardo (@leorw)
158
- * @since 2.3.1
159
- */
160
- continue;
161
- }
162
-
163
- if ( ( $prices->has_monthly() && $prices->monthly_price > 1.0 ) ||
164
- ( $prices->has_annual() && $prices->annual_price > 1.0 ) ||
165
- ( $prices->has_lifetime() && $prices->lifetime_price > 1.0 )
166
- ) {
167
- $filtered_pricing[] = $prices;
168
- }
169
- }
170
-
171
- if ( ! empty( $filtered_pricing ) ) {
172
- $has_paid_plan = true;
173
-
174
- $plan->pricing = $filtered_pricing;
175
-
176
- $has_pricing = true;
177
- }
178
- }
179
-
180
- if ( is_array( $features ) && 0 < count( $features ) ) {
181
- $plan->features = $features;
182
-
183
- $has_features = true;
184
- }
185
- }
186
- }
187
- }
188
-
189
- $latest = null;
190
-
191
- if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) {
192
- $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository(
193
- 'plugin_information', (object) array(
194
- 'slug' => $selected_addon->slug,
195
- 'is_ssl' => is_ssl(),
196
- 'fields' => array(
197
- 'banners' => true,
198
- 'reviews' => true,
199
- 'downloaded' => false,
200
- 'active_installs' => true
201
- )
202
- ) );
203
-
204
- if ( ! empty( $repo_data ) ) {
205
- $data = $repo_data;
206
- $data->wp_org_missing = false;
207
- } else {
208
- // Couldn't find plugin on .org.
209
- $selected_addon->is_wp_org_compliant = false;
210
-
211
- // Plugin is missing, not on Freemius nor WP.org.
212
- $data->wp_org_missing = true;
213
- }
214
-
215
- $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing );
216
- } else {
217
- $data->has_purchased_license = false;
218
- $data->wp_org_missing = false;
219
-
220
- $fs_addon = null;
221
- $current_addon_version = false;
222
- if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) {
223
- $fs_addon = $this->_fs->get_addon_instance( $selected_addon->id );
224
- $current_addon_version = $fs_addon->get_plugin_version();
225
- } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) {
226
- $addon_plugin_data = get_plugin_data(
227
- ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ),
228
- false,
229
- false
230
- );
231
-
232
- if ( ! empty( $addon_plugin_data ) ) {
233
- $current_addon_version = $addon_plugin_data['Version'];
234
- }
235
- }
236
-
237
- // Fetch latest version from Freemius.
238
- $latest = $this->_fs->_fetch_latest_version(
239
- $selected_addon->id,
240
- true,
241
- WP_FS__TIME_24_HOURS_IN_SEC,
242
- $current_addon_version
243
- );
244
-
245
- if ( $has_paid_plan ) {
246
- $blog_id = fs_request_get( 'fs_blog_id' );
247
- $has_valid_blog_id = is_numeric( $blog_id );
248
-
249
- if ( $has_valid_blog_id ) {
250
- switch_to_blog( $blog_id );
251
- }
252
-
253
- $data->checkout_link = $this->_fs->checkout_url(
254
- WP_FS__PERIOD_ANNUALLY,
255
- false,
256
- array(),
257
- ( $has_valid_blog_id ? false : null )
258
- );
259
-
260
- if ( $has_valid_blog_id ) {
261
- restore_current_blog();
262
- }
263
- }
264
-
265
- /**
266
- * Check if there's a purchased license in case the add-on can only be installed/downloaded as part of a purchased bundle.
267
- *
268
- * @author Leo Fajardo (@leorw)
269
- * @since 2.4.1
270
- */
271
- if ( is_object( $fs_addon ) ) {
272
- $data->has_purchased_license = $fs_addon->has_active_valid_license();
273
- } else {
274
- $account_addons = $this->_fs->get_account_addons();
275
- if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) {
276
- $data->has_purchased_license = true;
277
- }
278
- }
279
-
280
- if ( $has_free_plan || $data->has_purchased_license ) {
281
- $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id );
282
- }
283
-
284
- $data->fs_missing = (
285
- false === $latest &&
286
- (
287
- empty( $selected_addon->premium_releases_count ) ||
288
- ! ( $selected_addon->premium_releases_count > 0 )
289
- )
290
- );
291
-
292
- // Fetch as much as possible info from local files.
293
- $plugin_local_data = $this->_fs->get_plugin_data();
294
- $data->author = $plugin_local_data['Author'];
295
-
296
- if ( ! empty( $selected_addon->info->banner_url ) ) {
297
- $data->banners = array(
298
- 'low' => $selected_addon->info->banner_url,
299
- );
300
- }
301
-
302
- if ( ! empty( $selected_addon->info->screenshots ) ) {
303
- $view_vars = array(
304
- 'screenshots' => $selected_addon->info->screenshots,
305
- 'plugin' => $selected_addon,
306
- );
307
- $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars );
308
- }
309
-
310
- if ( is_object( $latest ) ) {
311
- $data->version = $latest->version;
312
- $data->last_updated = $latest->created;
313
- $data->requires = $latest->requires_platform_version;
314
- $data->tested = $latest->tested_up_to_version;
315
- } else if ( ! empty( $current_addon_version ) ) {
316
- $data->version = $current_addon_version;
317
- } else {
318
- // Add dummy version.
319
- $data->version = '1.0.0';
320
-
321
- // Add message to developer to deploy the plugin through Freemius.
322
- }
323
- }
324
-
325
- $data->name = $selected_addon->title;
326
- $view_vars = array( 'plugin' => $selected_addon );
327
-
328
- if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) {
329
- $latest_version_readme_data = $latest->readme;
330
- if ( isset( $latest_version_readme_data->sections ) ) {
331
- $data->sections = (array) $latest_version_readme_data->sections;
332
- } else {
333
- $data->sections = array();
334
- }
335
- }
336
-
337
- $data->sections['description'] = fs_get_template( '/plugin-info/description.php', $view_vars );
338
-
339
- if ( $has_pricing ) {
340
- // Add plans to data.
341
- $data->plans = $plans;
342
-
343
- if ( $has_features ) {
344
- $view_vars = array(
345
- 'plans' => $plans,
346
- 'plugin' => $selected_addon,
347
- );
348
- $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars );
349
- }
350
- }
351
-
352
- $data->has_free_plan = $has_free_plan;
353
- $data->has_paid_plan = $has_paid_plan;
354
- $data->is_paid = $has_paid_plan;
355
- $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant;
356
- $data->premium_slug = $selected_addon->premium_slug;
357
- $data->addon_id = $selected_addon->id;
358
-
359
- if ( ! isset( $data->has_purchased_license ) ) {
360
- $data->has_purchased_license = false;
361
- }
362
-
363
- return $data;
364
- }
365
-
366
- /**
367
- * @author Vova Feldman (@svovaf)
368
- * @since 1.1.7
369
- *
370
- * @param FS_Plugin_Plan $plan
371
- *
372
- * @return string
373
- */
374
- private function get_billing_cycle( FS_Plugin_Plan $plan ) {
375
- $billing_cycle = null;
376
-
377
- if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) {
378
- $pricing = $plan->pricing[0];
379
- if ( isset( $pricing->annual_price ) ) {
380
- $billing_cycle = 'annual';
381
- } else if ( isset( $pricing->monthly_price ) ) {
382
- $billing_cycle = 'monthly';
383
- } else if ( isset( $pricing->lifetime_price ) ) {
384
- $billing_cycle = 'lifetime';
385
- }
386
- } else {
387
- foreach ( $plan->pricing as $pricing ) {
388
- if ( isset( $pricing->annual_price ) ) {
389
- $billing_cycle = 'annual';
390
- } else if ( isset( $pricing->monthly_price ) ) {
391
- $billing_cycle = 'monthly';
392
- } else if ( isset( $pricing->lifetime_price ) ) {
393
- $billing_cycle = 'lifetime';
394
- }
395
-
396
- if ( ! is_null( $billing_cycle ) ) {
397
- break;
398
- }
399
- }
400
- }
401
-
402
- return $billing_cycle;
403
- }
404
-
405
- /**
406
- * @author Vova Feldman (@svovaf)
407
- * @since 2.0.0
408
- *
409
- * @param FS_Plugin_Plan $plan
410
- * @param FS_Pricing $pricing
411
- *
412
- * @return float|null|string
413
- */
414
- private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) {
415
- $price_tag = '';
416
- if ( isset( $pricing->annual_price ) ) {
417
- $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' );
418
- } else if ( isset( $pricing->monthly_price ) ) {
419
- $price_tag = $pricing->monthly_price . ' / mo';
420
- } else if ( isset( $pricing->lifetime_price ) ) {
421
- $price_tag = $pricing->lifetime_price;
422
- }
423
-
424
- return '$' . $price_tag;
425
- }
426
-
427
- /**
428
- * @author Leo Fajardo (@leorw)
429
- * @since 2.3.0
430
- *
431
- * @param object $api
432
- * @param FS_Plugin_Plan $plan
433
- *
434
- * @return string
435
- */
436
- private function get_actions_dropdown( $api, $plan = null ) {
437
- $this->actions = isset( $this->actions ) ?
438
- $this->actions :
439
- $this->get_plugin_actions( $api );
440
-
441
- $actions = $this->actions;
442
-
443
- $checkout_cta = $this->get_checkout_cta( $api, $plan );
444
- if ( ! empty( $checkout_cta ) ) {
445
- /**
446
- * If there's no license yet, make the checkout button the main CTA. Otherwise, make it the last item in
447
- * the actions dropdown.
448
- *
449
- * @author Leo Fajardo (@leorw)
450
- * @since 2.3.0
451
- */
452
- if ( ! $api->has_purchased_license ) {
453
- array_unshift( $actions, $checkout_cta );
454
- } else {
455
- $actions[] = $checkout_cta;
456
- }
457
- }
458
-
459
- if ( empty( $actions ) ) {
460
- return '';
461
- }
462
-
463
- $total_actions = count( $actions );
464
- if ( 1 === $total_actions ) {
465
- return $actions[0];
466
- }
467
-
468
- ob_start();
469
-
470
- ?>
471
- <div class="fs-cta fs-dropdown">
472
- <div class="button-group">
473
- <?php
474
- // This should NOT be sanitized as the $actions are HTML buttons already.
475
- echo $actions[0] ?>
476
- <div class="button button-primary fs-dropdown-arrow-button">
477
- <span class="fs-dropdown-arrow"></span>
478
- <ul class="fs-dropdown-list" style="display: none">
479
- <?php for ( $i = 1; $i < $total_actions; $i ++ ) : ?>
480
- <li><?php echo str_replace( 'button button-primary', '', $actions[ $i ] ) ?></li>
481
- <?php endfor ?>
482
- </ul>
483
- </div>
484
- </div>
485
- </div>
486
- <?php
487
-
488
- return ob_get_clean();
489
- }
490
-
491
- /**
492
- * @author Vova Feldman (@svovaf)
493
- * @since 1.1.7
494
- *
495
- * @param object $api
496
- * @param FS_Plugin_Plan $plan
497
- *
498
- * @return string
499
- */
500
- private function get_checkout_cta( $api, $plan = null ) {
501
- if ( empty( $api->checkout_link ) ||
502
- ! isset( $api->plans ) ||
503
- ! is_array( $api->plans ) ||
504
- 0 == count( $api->plans )
505
- ) {
506
- return '';
507
- }
508
-
509
- if ( is_null( $plan ) ) {
510
- foreach ( $api->plans as $p ) {
511
- if ( ! empty( $p->pricing ) ) {
512
- $plan = $p;
513
- break;
514
- }
515
- }
516
- }
517
-
518
- $blog_id = fs_request_get( 'fs_blog_id' );
519
- $has_valid_blog_id = is_numeric( $blog_id );
520
-
521
- if ( $has_valid_blog_id ) {
522
- switch_to_blog( $blog_id );
523
- }
524
-
525
- $addon_checkout_url = $this->_fs->addon_checkout_url(
526
- $plan->plugin_id,
527
- $plan->pricing[0]->id,
528
- $this->get_billing_cycle( $plan ),
529
- $plan->has_trial(),
530
- ( $has_valid_blog_id ? false : null )
531
- );
532
-
533
- if ( $has_valid_blog_id ) {
534
- restore_current_blog();
535
- }
536
-
537
- return '<a class="button button-primary fs-checkout-button right" href="' . $addon_checkout_url . '" target="_parent">' .
538
- esc_html( ! $plan->has_trial() ?
539
- (
540
- $api->has_purchased_license ?
541
- fs_text_inline( 'Purchase More', 'purchase-more', $api->slug ) :
542
- fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug )
543
- ) :
544
- sprintf(
545
- /* translators: %s: N-days trial */
546
- fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ),
547
- $this->get_trial_period( $plan )
548
- )
549
- ) .
550
- '</a>';
551
- }
552
-
553
- /**
554
- * @author Leo Fajardo (@leorw)
555
- * @since 2.3.0
556
- *
557
- * @param object $api
558
- *
559
- * @return string[]
560
- */
561
- private function get_plugin_actions( $api ) {
562
- $this->status = isset( $this->status ) ?
563
- $this->status :
564
- install_plugin_install_status( $api );
565
-
566
- $is_update_available = ( 'update_available' === $this->status['status'] );
567
-
568
- if ( $is_update_available && empty( $this->status['url'] ) ) {
569
- return array();
570
- }
571
-
572
- $blog_id = fs_request_get( 'fs_blog_id' );
573
-
574
- $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $blog_id );
575
-
576
- $actions = array();
577
-
578
- $is_addon_activated = $this->_fs->is_addon_activated( $api->slug );
579
- $fs_addon = null;
580
-
581
- $is_free_installed = null;
582
- $is_premium_installed = null;
583
-
584
- $has_installed_version = ( 'install' !== $this->status['status'] );
585
-
586
- if ( ! $api->has_paid_plan && ! $api->has_purchased_license ) {
587
- /**
588
- * Free-only add-on.
589
- *
590
- * @author Leo Fajardo (@leorw)
591
- * @since 2.3.0
592
- */
593
- $is_free_installed = $has_installed_version;
594
- $is_premium_installed = false;
595
- } else if ( ! $api->has_free_plan ) {
596
- /**
597
- * Premium-only add-on.
598
- *
599
- * @author Leo Fajardo (@leorw)
600
- * @since 2.3.0
601
- */
602
- $is_free_installed = false;
603
- $is_premium_installed = $has_installed_version;
604
- } else {
605
- /**
606
- * Freemium add-on.
607
- *
608
- * @author Leo Fajardo (@leorw)
609
- * @since 2.3.0
610
- */
611
- if ( ! $has_installed_version ) {
612
- $is_free_installed = false;
613
- $is_premium_installed = false;
614
- } else {
615
- $fs_addon = $is_addon_activated ?
616
- $this->_fs->get_addon_instance( $api->slug ) :
617
- null;
618
-
619
- if ( is_object( $fs_addon ) ) {
620
- if ( $fs_addon->is_premium() ) {
621
- $is_premium_installed = true;
622
- } else {
623
- $is_free_installed = true;
624
- }
625
- }
626
-
627
- if ( is_null( $is_free_installed ) ) {
628
- $is_free_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php" ) );
629
- if ( ! $is_free_installed ) {
630
- /**
631
- * Check if there's a plugin installed in a directory named `$api->slug`.
632
- *
633
- * @author Leo Fajardo (@leorw)
634
- * @since 2.3.0
635
- */
636
- $installed_plugins = get_plugins( '/' . $api->slug );
637
- $is_free_installed = ( ! empty( $installed_plugins ) );
638
- }
639
- }
640
-
641
- if ( is_null( $is_premium_installed ) ) {
642
- $is_premium_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php" ) );
643
- if ( ! $is_premium_installed ) {
644
- /**
645
- * Check if there's a plugin installed in a directory named `$api->premium_slug`.
646
- *
647
- * @author Leo Fajardo (@leorw)
648
- * @since 2.3.0
649
- */
650
- $installed_plugins = get_plugins( '/' . $api->premium_slug );
651
- $is_premium_installed = ( ! empty( $installed_plugins ) );
652
- }
653
- }
654
- }
655
-
656
- $has_installed_version = ( $is_free_installed || $is_premium_installed );
657
- }
658
-
659
- $this->status['is_free_installed'] = $is_free_installed;
660
- $this->status['is_premium_installed'] = $is_premium_installed;
661
-
662
- $can_install_free_version = false;
663
- $can_install_free_version_update = false;
664
- $can_download_free_version = false;
665
- $can_activate_free_version = false;
666
- $can_install_premium_version = false;
667
- $can_install_premium_version_update = false;
668
- $can_download_premium_version = false;
669
- $can_activate_premium_version = false;
670
-
671
- if ( ! $api->has_purchased_license ) {
672
- if ( $api->has_free_plan ) {
673
- if ( $has_installed_version ) {
674
- if ( $is_update_available ) {
675
- $can_install_free_version_update = true;
676
- } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) {
677
- $can_activate_free_version = true;
678
- }
679
- } else {
680
- if (
681
- $this->_fs->is_premium() ||
682
- ! $this->_fs->is_org_repo_compliant() ||
683
- $api->is_wp_org_compliant
684
- ) {
685
- $can_install_free_version = true;
686
- } else {
687
- $can_download_free_version = true;
688
- }
689
- }
690
- }
691
- } else {
692
- if ( ! is_object( $fs_addon ) && $is_addon_activated ) {
693
- $fs_addon = $this->_fs->get_addon_instance( $api->slug );
694
- }
695
-
696
- $can_download_premium_version = true;
697
-
698
- if ( ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) {
699
- if ( $is_premium_installed ) {
700
- $can_activate_premium_version = ( ! $is_addon_activated || ! $fs_addon->is_premium() );
701
- } else if ( $is_free_installed ) {
702
- $can_activate_free_version = ( ! $is_addon_activated );
703
- }
704
- }
705
-
706
- if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() ) {
707
- if ( $is_update_available ) {
708
- $can_install_premium_version_update = true;
709
- } else if ( ! $is_premium_installed ) {
710
- $can_install_premium_version = true;
711
- }
712
- }
713
- }
714
-
715
- if (
716
- $can_install_premium_version ||
717
- $can_install_premium_version_update
718
- ) {
719
- if ( is_numeric( $blog_id ) ) {
720
- /**
721
- * Replace the network status URL with a blog admin–based status URL if the `Add-Ons` page is loaded
722
- * from a specific blog admin page (when `fs_blog_id` is valid) in order for plugin installation/update
723
- * to work.
724
- *
725
- * @author Leo Fajardo (@leorw)
726
- * @since 2.3.0
727
- */
728
- $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] );
729
- }
730
-
731
- /**
732
- * Add the `fs_allow_updater_and_dialog` param to the install/update URL so that the add-on can be
733
- * installed/updated.
734
- *
735
- * @author Leo Fajardo (@leorw)
736
- * @since 2.3.0
737
- */
738
- $this->status['url'] = str_replace( '?', '?fs_allow_updater_and_dialog=true&amp;', $this->status['url'] );
739
- }
740
-
741
- if ( $can_install_free_version_update || $can_install_premium_version_update ) {
742
- $actions[] = $this->get_cta(
743
- ( $can_install_free_version_update ?
744
- fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) :
745
- fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ),
746
- true,
747
- false,
748
- $this->status['url'],
749
- '_parent'
750
- );
751
- } else if ( $can_install_free_version || $can_install_premium_version ) {
752
- $actions[] = $this->get_cta(
753
- ( $can_install_free_version ?
754
- fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) :
755
- fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ),
756
- true,
757
- false,
758
- $this->status['url'],
759
- '_parent'
760
- );
761
- }
762
-
763
- $download_latest_action = '';
764
-
765
- if (
766
- ! empty( $api->download_link ) &&
767
- ( $can_download_free_version || $can_download_premium_version )
768
- ) {
769
- $download_latest_action = $this->get_cta(
770
- ( $can_download_free_version ?
771
- fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) :
772
- fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ),
773
- true,
774
- false,
775
- esc_url( $api->download_link )
776
- );
777
- }
778
-
779
- if ( ! $can_activate_free_version && ! $can_activate_premium_version ) {
780
- if ( ! empty( $download_latest_action ) ) {
781
- $actions[] = $download_latest_action;
782
- }
783
- } else {
784
- $activate_action = sprintf(
785
- '<a class="button button-primary edit" href="%s" title="%s" target="_parent">%s</a>',
786
- wp_nonce_url( ( is_numeric( $blog_id ) ? trailingslashit( get_admin_url( $blog_id ) ) : '' ) . 'plugins.php?action=activate&amp;plugin=' . $this->status['file'], 'activate-plugin_' . $this->status['file'] ),
787
- fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $api->slug ),
788
- $can_activate_free_version ?
789
- fs_text_inline( 'Activate Free Version', 'activate-free', $api->slug ) :
790
- fs_text_inline( 'Activate', 'activate', $api->slug )
791
- );
792
-
793
- if ( ! $can_download_premium_version && ! empty( $download_latest_action ) ) {
794
- $actions[] = $download_latest_action;
795
-
796
- $download_latest_action = '';
797
- }
798
-
799
- if ( $can_install_premium_version || $can_install_premium_version_update ) {
800
- if ( $can_download_premium_version && ! empty( $download_latest_action ) ) {
801
- $actions[] = $download_latest_action;
802
-
803
- $download_latest_action = '';
804
- }
805
-
806
- $actions[] = $activate_action;
807
- } else {
808
- array_unshift( $actions, $activate_action );
809
- }
810
-
811
- if ( ! empty ($download_latest_action ) ) {
812
- $actions[] = $download_latest_action;
813
- }
814
- }
815
-
816
- return $actions;
817
- }
818
-
819
- /**
820
- * Rebuilds the status URL based on the admin URL.
821
- *
822
- * @author Leo Fajardo (@leorw)
823
- * @since 2.3.0
824
- *
825
- * @param int $blog_id
826
- * @param string $network_status_url
827
- * @param string $status
828
- *
829
- * @return string
830
- */
831
- private static function get_blog_status_url( $blog_id, $network_status_url, $status ) {
832
- if ( ! in_array( $status, array( 'install', 'update_available' ) ) ) {
833
- return $network_status_url;
834
- }
835
-
836
- $action = ( 'install' === $status ) ?
837
- 'install-plugin' :
838
- 'upgrade-plugin';
839
-
840
- $query = parse_url( $network_status_url, PHP_URL_QUERY );
841
- if ( empty( $query ) ) {
842
- return $network_status_url;
843
- }
844
-
845
- parse_str( html_entity_decode( $query ), $url_params );
846
- if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) {
847
- return $network_status_url;
848
- }
849
-
850
- $plugin = $url_params['plugin'];
851
-
852
- return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}");
853
- }
854
-
855
- /**
856
- * Helper method to get a CTA button HTML.
857
- *
858
- * @author Vova Feldman (@svovaf)
859
- * @since 2.0.0
860
- *
861
- * @param string $label
862
- * @param bool $is_primary
863
- * @param bool $is_disabled
864
- * @param string $href
865
- * @param string $target
866
- *
867
- * @return string
868
- */
869
- private function get_cta(
870
- $label,
871
- $is_primary = true,
872
- $is_disabled = false,
873
- $href = '',
874
- $target = '_blank'
875
- ) {
876
- $classes = array();
877
-
878
- if ( ! $is_primary ) {
879
- $classes[] = 'left';
880
- } else {
881
- $classes[] = 'button-primary';
882
- $classes[] = 'right';
883
- }
884
-
885
- if ( $is_disabled ) {
886
- $classes[] = 'disabled';
887
- }
888
-
889
- $rel = ( '_blank' === $target ) ? ' rel="noopener noreferrer"' : '';
890
-
891
- return sprintf(
892
- '<a %s class="button %s">%s</a>',
893
- empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel,
894
- implode( ' ', $classes ),
895
- $label
896
- );
897
- }
898
-
899
- /**
900
- * @author Vova Feldman (@svovaf)
901
- * @since 1.1.7
902
- *
903
- * @param FS_Plugin_Plan $plan
904
- *
905
- * @return string
906
- */
907
- private function get_trial_period( $plan ) {
908
- $trial_period = (int) $plan->trial_period;
909
-
910
- switch ( $trial_period ) {
911
- case 30:
912
- return 'month';
913
- case 60:
914
- return '2 months';
915
- default:
916
- return "{$plan->trial_period} days";
917
- }
918
- }
919
-
920
- /**
921
- * Display plugin information in dialog box form.
922
- *
923
- * Based on core install_plugin_information() function.
924
- *
925
- * @author Vova Feldman (@svovaf)
926
- * @since 1.0.6
927
- */
928
- function install_plugin_information() {
929
- global $tab;
930
-
931
- if ( empty( $_REQUEST['plugin'] ) ) {
932
- return;
933
- }
934
-
935
- $args = array(
936
- 'slug' => wp_unslash( $_REQUEST['plugin'] ),
937
- 'is_ssl' => is_ssl(),
938
- 'fields' => array(
939
- 'banners' => true,
940
- 'reviews' => true,
941
- 'downloaded' => false,
942
- 'active_installs' => true
943
- )
944
- );
945
-
946
- if ( is_array( $args ) ) {
947
- $args = (object) $args;
948
- }
949
-
950
- if ( ! isset( $args->per_page ) ) {
951
- $args->per_page = 24;
952
- }
953
-
954
- if ( ! isset( $args->locale ) ) {
955
- $args->locale = get_locale();
956
- }
957
-
958
- $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args );
959
-
960
- if ( is_wp_error( $api ) ) {
961
- wp_die( $api );
962
- }
963
-
964
- $plugins_allowedtags = array(
965
- 'a' => array(
966
- 'href' => array(),
967
- 'title' => array(),
968
- 'target' => array(),
969
- // Add image style for screenshots.
970
- 'class' => array()
971
- ),
972
- 'style' => array(),
973
- 'abbr' => array( 'title' => array() ),
974
- 'acronym' => array( 'title' => array() ),
975
- 'code' => array(),
976
- 'pre' => array(),
977
- 'em' => array(),
978
- 'strong' => array(),
979
- 'div' => array( 'class' => array() ),
980
- 'span' => array( 'class' => array() ),
981
- 'p' => array(),
982
- 'ul' => array(),
983
- 'ol' => array(),
984
- 'li' => array( 'class' => array() ),
985
- 'i' => array( 'class' => array() ),
986
- 'h1' => array(),
987
- 'h2' => array(),
988
- 'h3' => array(),
989
- 'h4' => array(),
990
- 'h5' => array(),
991
- 'h6' => array(),
992
- 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ),
993
- // 'table' => array(),
994
- // 'td' => array(),
995
- // 'tr' => array(),
996
- // 'th' => array(),
997
- // 'thead' => array(),
998
- // 'tbody' => array(),
999
- );
1000
-
1001
- $plugins_section_titles = array(
1002
- 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ),
1003
- 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ),
1004
- 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ),
1005
- 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ),
1006
- 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ),
1007
- 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ),
1008
- 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ),
1009
- );
1010
-
1011
- // Sanitize HTML
1012
- // foreach ( (array) $api->sections as $section_name => $content ) {
1013
- // $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags );
1014
- // }
1015
-
1016
- foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) {
1017
- if ( isset( $api->$key ) ) {
1018
- $api->$key = wp_kses( $api->$key, $plugins_allowedtags );
1019
- }
1020
- }
1021
-
1022
- // Add after $api->slug is ready.
1023
- $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug );
1024
-
1025
- $_tab = esc_attr( $tab );
1026
-
1027
- $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English.
1028
- if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) {
1029
- $section_titles = array_keys( (array) $api->sections );
1030
- $section = array_shift( $section_titles );
1031
- }
1032
-
1033
- iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) );
1034
-
1035
- $_with_banner = '';
1036
-
1037
- // var_dump($api->banners);
1038
- if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) {
1039
- $_with_banner = 'with-banner';
1040
- $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low'];
1041
- $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high'];
1042
- ?>
1043
- <style type="text/css">
1044
- #plugin-information-title.with-banner
1045
- {
1046
- background-image: url( <?php echo esc_url( $low ); ?> );
1047
- }
1048
-
1049
- @media only screen and ( -webkit-min-device-pixel-ratio: 1.5 )
1050
- {
1051
- #plugin-information-title.with-banner
1052
- {
1053
- background-image: url( <?php echo esc_url( $high ); ?> );
1054
- }
1055
- }
1056
- </style>
1057
- <?php
1058
- }
1059
-
1060
- echo '<div id="plugin-information-scrollable">';
1061
- echo "<div id='{$_tab}-title' class='{$_with_banner}'><div class='vignette'></div><h2>{$api->name}</h2></div>";
1062
- echo "<div id='{$_tab}-tabs' class='{$_with_banner}'>\n";
1063
-
1064
- foreach ( (array) $api->sections as $section_name => $content ) {
1065
- if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) {
1066
- continue;
1067
- }
1068
-
1069
- if ( isset( $plugins_section_titles[ $section_name ] ) ) {
1070
- $title = $plugins_section_titles[ $section_name ];
1071
- } else {
1072
- $title = ucwords( str_replace( '_', ' ', $section_name ) );
1073
- }
1074
-
1075
- $class = ( $section_name === $section ) ? ' class="current"' : '';
1076
- $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) );
1077
- $href = esc_url( $href );
1078
- $san_section = esc_attr( $section_name );
1079
- echo "\t<a name='$san_section' href='$href' $class>" . esc_html( $title ) . "</a>\n";
1080
- }
1081
-
1082
- echo "</div>\n";
1083
-
1084
- ?>
1085
- <div id="<?php echo $_tab; ?>-content" class='<?php echo $_with_banner; ?>'>
1086
- <div class="fyi">
1087
- <?php if ( $api->is_paid ) : ?>
1088
- <?php if ( isset( $api->plans ) ) : ?>
1089
- <div class="plugin-information-pricing">
1090
- <?php foreach ( $api->plans as $plan ) : ?>
1091
- <?php
1092
- if ( empty( $plan->pricing ) ) {
1093
- continue;
1094
- }
1095
-
1096
- /**
1097
- * @var FS_Plugin_Plan $plan
1098
- */
1099
- ?>
1100
- <?php $first_pricing = $plan->pricing[0] ?>
1101
- <?php $is_multi_cycle = $first_pricing->is_multi_cycle() ?>
1102
- <div class="fs-plan<?php if ( ! $is_multi_cycle ) {
1103
- echo ' fs-single-cycle';
1104
- } ?>" data-plan-id="<?php echo $plan->id ?>">
1105
- <h3 data-plan="<?php echo $plan->id ?>"><?php echo esc_html( sprintf( fs_text_x_inline( '%s Plan', 'e.g. Professional Plan', 'x-plan', $api->slug ), $plan->title ) ) ?></h3>
1106
- <?php $has_annual = $first_pricing->has_annual() ?>
1107
- <?php $has_monthly = $first_pricing->has_monthly() ?>
1108
- <div class="nav-tab-wrapper">
1109
- <?php $billing_cycles = array( 'monthly', 'annual', 'lifetime' ) ?>
1110
- <?php $i = 0;
1111
- foreach ( $billing_cycles as $cycle ) : ?>
1112
- <?php $prop = "{$cycle}_price";
1113
- if ( isset( $first_pricing->{$prop} ) ) : ?>
1114
- <?php $is_featured = ( 'annual' === $cycle && $is_multi_cycle ) ?>
1115
- <?php
1116
- $prices = array();
1117
- foreach ( $plan->pricing as $pricing ) {
1118
- if ( isset( $pricing->{$prop} ) ) {
1119
- $prices[] = array(
1120
- 'id' => $pricing->id,
1121
- 'licenses' => $pricing->licenses,
1122
- 'price' => $pricing->{$prop}
1123
- );
1124
- }
1125
- }
1126
- ?>
1127
- <a class="nav-tab" data-billing-cycle="<?php echo $cycle ?>"
1128
- data-pricing="<?php echo esc_attr( json_encode( $prices ) ) ?>">
1129
- <?php if ( $is_featured ) : ?>
1130
- <label>
1131
- &#9733; <?php fs_esc_html_echo_x_inline( 'Best', 'e.g. the best product', 'best', $api->slug ) ?>
1132
- &#9733;</label>
1133
- <?php endif ?>
1134
- <?php
1135
- switch ( $cycle ) {
1136
- case 'monthly':
1137
- fs_esc_html_echo_x_inline( 'Monthly', 'as every month', 'monthly', $api->slug );
1138
- break;
1139
- case 'annual':
1140
- fs_esc_html_echo_x_inline( 'Annual', 'as once a year', 'annual', $api->slug );
1141
- break;
1142
- case 'lifetime':
1143
- fs_esc_html_echo_inline( 'Lifetime', 'lifetime', $api->slug );
1144
- break;
1145
- }
1146
- ?>
1147
- </a>
1148
- <?php endif ?>
1149
- <?php $i ++; endforeach ?>
1150
- <?php wp_enqueue_script( 'jquery' ) ?>
1151
- <script type="text/javascript">
1152
- (function ($, undef) {
1153
- var
1154
- _formatBillingFrequency = function (cycle) {
1155
- switch (cycle) {
1156
- case 'monthly':
1157
- return '<?php printf( fs_text_x_inline( 'Billed %s', 'e.g. billed monthly', 'billed-x', $api->slug ), fs_text_x_inline( 'Monthly', 'as every month', 'monthly', $api->slug ) ) ?>';
1158
- case 'annual':
1159
- return '<?php printf( fs_text_x_inline( 'Billed %s', 'e.g. billed monthly', 'billed-x', $api->slug ), fs_text_x_inline( 'Annually', 'as once a year', 'annually', $api->slug ) ) ?>';
1160
- case 'lifetime':
1161
- return '<?php printf( fs_text_x_inline( 'Billed %s', 'e.g. billed monthly', 'billed-x', $api->slug ), fs_text_x_inline( 'Once', 'as once a year', 'once', $api->slug ) ) ?>';
1162
- }
1163
- },
1164
- _formatLicensesTitle = function (pricing) {
1165
- switch (pricing.licenses) {
1166
- case 1:
1167
- return '<?php fs_esc_attr_echo_inline( 'Single Site License', 'license-single-site', $api->slug ) ?>';
1168
- case null:
1169
- return '<?php fs_esc_attr_echo_inline( 'Unlimited Licenses', 'license-unlimited', $api->slug ) ?>';
1170
- default:
1171
- return '<?php fs_esc_attr_echo_inline( 'Up to %s Sites', 'license-x-sites', $api->slug ) ?>'.replace('%s', pricing.licenses);
1172
- }
1173
- },
1174
- _formatPrice = function (pricing, cycle, multipleLicenses) {
1175
- if (undef === multipleLicenses)
1176
- multipleLicenses = true;
1177
-
1178
- var priceCycle;
1179
- switch (cycle) {
1180
- case 'monthly':
1181
- priceCycle = ' / <?php fs_echo_x_inline( 'mo', 'as monthly period', 'mo', $api->slug ) ?>';
1182
- break;
1183
- case 'lifetime':
1184
- priceCycle = '';
1185
- break;
1186
- case 'annual':
1187
- default:
1188
- priceCycle = ' / <?php fs_echo_x_inline( 'year', 'as annual period', 'year', $api->slug ) ?>';
1189
- break;
1190
- }
1191
-
1192
- if (!multipleLicenses && 1 == pricing.licenses) {
1193
- return '$' + pricing.price + priceCycle;
1194
- }
1195
-
1196
- return _formatLicensesTitle(pricing) + ' - <var class="fs-price">$' + pricing.price + priceCycle + '</var>';
1197
- },
1198
- _checkoutUrl = function (plan, pricing, cycle) {
1199
- return '<?php echo esc_url_raw( remove_query_arg( 'billing_cycle', add_query_arg( array( 'plugin_id' => $plan->plugin_id ), $api->checkout_link ) ) ) ?>' +
1200
- '&plan_id=' + plan +
1201
- '&pricing_id=' + pricing +
1202
- '&billing_cycle=' + cycle<?php if ( $plan->has_trial() ) {
1203
- echo " + '&trial=true'";
1204
- }?>;
1205
- },
1206
- _updateCtaUrl = function (plan, pricing, cycle) {
1207
- $('.plugin-information-pricing .fs-checkout-button, #plugin-information-footer .fs-checkout-button').attr('href', _checkoutUrl(plan, pricing, cycle));
1208
- };
1209
-
1210
- $(document).ready(function () {
1211
- var $plan = $('.plugin-information-pricing .fs-plan[data-plan-id=<?php echo $plan->id ?>]');
1212
- $plan.find('input[type=radio]').on('click', function () {
1213
- _updateCtaUrl(
1214
- $plan.attr('data-plan-id'),
1215
- $(this).val(),
1216
- $plan.find('.nav-tab-active').attr('data-billing-cycle')
1217
- );
1218
-
1219
- $plan.find('.fs-trial-terms .fs-price').html(
1220
- $(this).parents('label').find('.fs-price').html()
1221
- );
1222
- });
1223
-
1224
- $plan.find('.nav-tab').click(function () {
1225
- if ($(this).hasClass('nav-tab-active'))
1226
- return;
1227
-
1228
- var $this = $(this),
1229
- billingCycle = $this.attr('data-billing-cycle'),
1230
- pricing = JSON.parse($this.attr('data-pricing')),
1231
- $pricesList = $this.parents('.fs-plan').find('.fs-pricing-body .fs-licenses'),
1232
- html = '';
1233
-
1234
- // Un-select previously selected tab.
1235
- $plan.find('.nav-tab').removeClass('nav-tab-active');
1236
-
1237
- // Select current tab.
1238
- $this.addClass('nav-tab-active');
1239
-
1240
- // Render licenses prices.
1241
- if (1 == pricing.length) {
1242
- html = '<li><label><?php echo fs_esc_attr_x_inline( 'Price', 'noun', 'price', $api->slug ) ?>: ' + _formatPrice(pricing[0], billingCycle, false) + '</label></li>';
1243
- } else {
1244
- for (var i = 0; i < pricing.length; i++) {
1245
- html += '<li><label><input name="pricing-<?php echo $plan->id ?>" type="radio" value="' + pricing[i].id + '">' + _formatPrice(pricing[i], billingCycle) + '</label></li>';
1246
- }
1247
- }
1248
- $pricesList.html(html);
1249
-
1250
- if (1 < pricing.length) {
1251
- // Select first license option.
1252
- $pricesList.find('li:first input').click();
1253
- }
1254
- else {
1255
- _updateCtaUrl(
1256
- $plan.attr('data-plan-id'),
1257
- pricing[0].id,
1258
- billingCycle
1259
- );
1260
- }
1261
-
1262
- // Update billing frequency.
1263
- $plan.find('.fs-billing-frequency').html(_formatBillingFrequency(billingCycle));
1264
-
1265
- if ('annual' === billingCycle) {
1266
- $plan.find('.fs-annual-discount').show();
1267
- } else {
1268
- $plan.find('.fs-annual-discount').hide();
1269
- }
1270
- });
1271
-
1272
- <?php if ( $has_annual ) : ?>
1273
- // Select annual by default.
1274
- $plan.find('.nav-tab[data-billing-cycle=annual]').click();
1275
- <?php else : ?>
1276
- // Select first tab.
1277
- $plan.find('.nav-tab:first').click();
1278
- <?php endif ?>
1279
- });
1280
- }(jQuery));
1281
- </script>
1282
- </div>
1283
- <div class="fs-pricing-body">
1284
- <span class="fs-billing-frequency"></span>
1285
- <?php $annual_discount = ( $has_annual && $has_monthly ) ? $plan->pricing[0]->annual_discount_percentage() : 0 ?>
1286
- <?php if ( $annual_discount > 0 ) : ?>
1287
- <span
1288
- class="fs-annual-discount"><?php printf(
1289
- /* translators: %s: Discount (e.g. discount of $5 or 10%) */
1290
- fs_esc_html_inline( 'Save %s', 'save-x', $api->slug ), $annual_discount . '%' ) ?></span>
1291
- <?php endif ?>
1292
- <ul class="fs-licenses">
1293
- </ul>
1294
- <?php echo $this->get_actions_dropdown( $api, $plan ) ?>
1295
- <div style="clear:both"></div>
1296
- <?php if ( $plan->has_trial() ) : ?>
1297
- <?php $trial_period = $this->get_trial_period( $plan ) ?>
1298
- <ul class="fs-trial-terms">
1299
- <li>
1300
- <i class="dashicons dashicons-yes"></i><?php echo esc_html( sprintf( fs_text_inline( 'No commitment for %s - cancel anytime', 'no-commitment-x', $api->slug ), $trial_period ) ) ?>
1301
- </li>
1302
- <li>
1303
- <i class="dashicons dashicons-yes"></i><?php printf( esc_html( fs_text_inline( 'After your free %s, pay as little as %s', 'after-x-pay-as-little-y', $api->slug ) ), $trial_period, '<var class="fs-price">' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '</var>' ) ?>
1304
- </li>
1305
- </ul>
1306
- <?php endif ?>
1307
- </div>
1308
- </div>
1309
- </div>
1310
- <?php endforeach ?>
1311
- <?php endif ?>
1312
- <?php endif ?>
1313
- <div>
1314
- <h3><?php fs_echo_inline( 'Details', 'details', $api->slug ) ?></h3>
1315
- <ul>
1316
- <?php if ( ! empty( $api->version ) ) { ?>
1317
- <li>
1318
- <strong><?php fs_esc_html_echo_x_inline( 'Version', 'product version', 'version', $api->slug ); ?>
1319
- :</strong> <?php echo $api->version; ?></li>
1320
- <?php
1321
- }
1322
- if ( ! empty( $api->author ) ) {
1323
- ?>
1324
- <li>
1325
- <strong><?php fs_echo_x_inline( 'Author', 'as the plugin author', 'author', $api->slug ); ?>
1326
- :</strong> <?php echo links_add_target( $api->author, '_blank' ); ?>
1327
- </li>
1328
- <?php
1329
- }
1330
- if ( ! empty( $api->last_updated ) ) {
1331
- ?>
1332
- <li><strong><?php fs_echo_inline( 'Last Updated', 'last-updated', $api->slug ); ?>
1333
- :</strong> <span
1334
- title="<?php echo $api->last_updated; ?>">
1335
- <?php echo esc_html( sprintf(
1336
- /* translators: %s: time period (e.g. "2 hours" ago) */
1337
- fs_text_x_inline( '%s ago', 'x-ago', $api->slug ),
1338
- human_time_diff( strtotime( $api->last_updated ) )
1339
- ) ) ?>
1340
- </span></li>
1341
- <?php
1342
- }
1343
- if ( ! empty( $api->requires ) ) {
1344
- ?>
1345
- <li>
1346
- <strong><?php fs_esc_html_echo_inline( 'Requires WordPress Version', 'requires-wordpress-version', $api->slug ) ?>
1347
- :</strong> <?php echo esc_html( sprintf( fs_text_inline( '%s or higher', 'x-or-higher', $api->slug ), $api->requires ) ) ?>
1348
- </li>
1349
- <?php
1350
- }
1351
- if ( ! empty( $api->tested ) ) {
1352
- ?>
1353
- <li>
1354
- <strong><?php fs_esc_html_echo_inline( 'Compatible up to', 'compatible-up-to', $api->slug ); ?>
1355
- :</strong> <?php echo $api->tested; ?>
1356
- </li>
1357
- <?php
1358
- }
1359
- if ( ! empty( $api->downloaded ) ) {
1360
- ?>
1361
- <li>
1362
- <strong><?php fs_esc_html_echo_inline( 'Downloaded', 'downloaded', $api->slug ) ?>
1363
- :</strong> <?php echo esc_html( sprintf(
1364
- ( ( 1 == $api->downloaded ) ?
1365
- /* translators: %s: 1 or One (Number of times downloaded) */
1366
- fs_text_inline( '%s time', 'x-time', $api->slug ) :
1367
- /* translators: %s: Number of times downloaded */
1368
- fs_text_inline( '%s times', 'x-times', $api->slug )
1369
- ),
1370
- number_format_i18n( $api->downloaded )
1371
- ) ); ?>
1372
- </li>
1373
- <?php
1374
- }
1375
- if ( ! empty( $api->slug ) && true == $api->is_wp_org_compliant ) {
1376
- ?>
1377
- <li><a target="_blank"
1378
- rel="noopener noreferrer"
1379
- href="https://wordpress.org/plugins/<?php echo $api->slug; ?>/"><?php fs_esc_html_echo_inline( 'WordPress.org Plugin Page', 'wp-org-plugin-page', $api->slug ) ?>
1380
- &#187;</a>
1381
- </li>
1382
- <?php
1383
- }
1384
- if ( ! empty( $api->homepage ) ) {
1385
- ?>
1386
- <li><a target="_blank"
1387
- rel="noopener noreferrer"
1388
- href="<?php echo esc_url( $api->homepage ); ?>"><?php fs_esc_html_echo_inline( 'Plugin Homepage', 'plugin-homepage', $api->slug ) ?>
1389
- &#187;</a>
1390
- </li>
1391
- <?php
1392
- }
1393
- if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) {
1394
- ?>
1395
- <li><a target="_blank"
1396
- rel="noopener noreferrer"
1397
- href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_esc_html_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1398
- &#187;</a>
1399
- </li>
1400
- <?php } ?>
1401
- </ul>
1402
- </div>
1403
- <?php if ( ! empty( $api->rating ) ) { ?>
1404
- <h3><?php fs_echo_inline( 'Average Rating', 'average-rating', $api->slug ); ?></h3>
1405
- <?php wp_star_rating( array(
1406
- 'rating' => $api->rating,
1407
- 'type' => 'percent',
1408
- 'number' => $api->num_ratings
1409
- ) ); ?>
1410
- <small>(<?php echo esc_html( sprintf(
1411
- fs_text_inline( 'based on %s', 'based-on-x', $api->slug ),
1412
- sprintf(
1413
- ( ( 1 == $api->num_ratings ) ?
1414
- /* translators: %s: 1 or One */
1415
- fs_text_inline( '%s rating', 'x-rating', $api->slug ) :
1416
- /* translators: %s: Number larger than 1 */
1417
- fs_text_inline( '%s ratings', 'x-ratings', $api->slug )
1418
- ),
1419
- number_format_i18n( $api->num_ratings )
1420
- ) ) ) ?>)
1421
- </small>
1422
- <?php
1423
- }
1424
-
1425
- if ( ! empty( $api->ratings ) && array_sum( (array) $api->ratings ) > 0 ) {
1426
- foreach ( $api->ratings as $key => $ratecount ) {
1427
- // Avoid div-by-zero.
1428
- $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0;
1429
- $stars_label = sprintf(
1430
- ( ( 1 == $key ) ?
1431
- /* translators: %s: 1 or One */
1432
- fs_text_inline( '%s star', 'x-star', $api->slug ) :
1433
- /* translators: %s: Number larger than 1 */
1434
- fs_text_inline( '%s stars', 'x-stars', $api->slug )
1435
- ),
1436
- number_format_i18n( $key )
1437
- );
1438
- ?>
1439
- <div class="counter-container">
1440
- <span class="counter-label"><a
1441
- href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>"
1442
- target="_blank"
1443
- rel="noopener noreferrer"
1444
- title="<?php echo esc_attr( sprintf(
1445
- /* translators: %s: # of stars (e.g. 5 stars) */
1446
- fs_text_inline( 'Click to see reviews that provided a rating of %s', 'click-to-reviews', $api->slug ),
1447
- $stars_label
1448
- ) ) ?>"><?php echo $stars_label ?></a></span>
1449
- <span class="counter-back">
1450
- <span class="counter-bar" style="width: <?php echo absint(92 * $_rating); ?>px;"></span>
1451
- </span>
1452
- <span class="counter-count"><?php echo number_format_i18n( $ratecount ); ?></span>
1453
- </div>
1454
- <?php
1455
- }
1456
- }
1457
- if ( ! empty( $api->contributors ) ) {
1458
- ?>
1459
- <h3><?php fs_echo_inline( 'Contributors', 'contributors', $api->slug ); ?></h3>
1460
- <ul class="contributors">
1461
- <?php
1462
- foreach ( (array) $api->contributors as $contrib_username => $contrib_profile ) {
1463
- if ( empty( $contrib_username ) && empty( $contrib_profile ) ) {
1464
- continue;
1465
- }
1466
- if ( empty( $contrib_username ) ) {
1467
- $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile );
1468
- }
1469
- $contrib_username = sanitize_user( $contrib_username );
1470
- if ( empty( $contrib_profile ) ) {
1471
- echo "<li><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</li>";
1472
- } else {
1473
- echo "<li><a href='{$contrib_profile}' target='_blank' rel='noopener noreferrer'><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</a></li>";
1474
- }
1475
- }
1476
- ?>
1477
- </ul>
1478
- <?php if ( ! empty( $api->donate_link ) ) { ?>
1479
- <a target="_blank"
1480
- rel="noopener noreferrer"
1481
- href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1482
- &#187;</a>
1483
- <?php } ?>
1484
- <?php } ?>
1485
- </div>
1486
- <div id="section-holder" class="wrap">
1487
- <?php
1488
- if ( ! empty( $api->tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) {
1489
- echo '<div class="notice notice-warning"><p>' . '<strong>' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ':</strong> ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '</p></div>';
1490
- } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) {
1491
- echo '<div class="notice notice-warning"><p>' . '<strong>' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ':</strong> ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '</p></div>';
1492
- }
1493
-
1494
- foreach ( (array) $api->sections as $section_name => $content ) {
1495
- $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' );
1496
- $content = links_add_target( $content, '_blank' );
1497
-
1498
- $san_section = esc_attr( $section_name );
1499
-
1500
- $display = ( $section_name === $section ) ? 'block' : 'none';
1501
-
1502
- if ( 'description' === $section_name &&
1503
- ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) ||
1504
- ( ! $api->is_wp_org_compliant && $api->fs_missing ) )
1505
- ) {
1506
- $missing_notice = array(
1507
- 'type' => 'error',
1508
- 'id' => md5( microtime() ),
1509
- 'message' => $api->is_paid ?
1510
- fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) :
1511
- fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ),
1512
- );
1513
- fs_require_template( 'admin-notice.php', $missing_notice );
1514
- }
1515
- echo "\t<div id='section-{$san_section}' class='section' style='display: {$display};'>\n";
1516
- echo $content;
1517
- echo "\t</div>\n";
1518
- }
1519
- echo "</div>\n";
1520
- echo "</div>\n";
1521
- echo "</div>\n"; // #plugin-information-scrollable
1522
- echo "<div id='$tab-footer'>\n";
1523
-
1524
- if (
1525
- ! empty( $api->download_link ) &&
1526
- ! empty( $this->status ) &&
1527
- in_array( $this->status['status'], array( 'newer_installed', 'latest_installed' ) )
1528
- ) {
1529
- if ( 'newer_installed' === $this->status['status'] ) {
1530
- echo $this->get_cta(
1531
- ( $this->status['is_premium_installed'] ?
1532
- esc_html( sprintf( fs_text_inline( 'Newer Version (%s) Installed', 'newer-installed', $api->slug ), $this->status['version'] ) ) :
1533
- esc_html( sprintf( fs_text_inline( 'Newer Free Version (%s) Installed', 'newer-free-installed', $api->slug ), $this->status['version'] ) ) ),
1534
- false,
1535
- true
1536
- );
1537
- } else {
1538
- echo $this->get_cta(
1539
- ( $this->status['is_premium_installed'] ?
1540
- fs_esc_html_inline( 'Latest Version Installed', 'latest-installed', $api->slug ) :
1541
- fs_esc_html_inline( 'Latest Free Version Installed', 'latest-free-installed', $api->slug ) ),
1542
- false,
1543
- true
1544
- );
1545
- }
1546
- }
1547
-
1548
- echo $this->get_actions_dropdown( $api, null );
1549
-
1550
- echo "</div>\n";
1551
- ?>
1552
- <script type="text/javascript">
1553
- ( function( $, undef ) {
1554
- var $dropdowns = $( '.fs-dropdown' );
1555
-
1556
- $( '#plugin-information' )
1557
- .click( function( evt ) {
1558
- var $target = $( evt.target );
1559
-
1560
- if (
1561
- $target.hasClass( 'fs-dropdown-arrow-button' ) ||
1562
- ( 0 !== $target.parents( '.fs-dropdown-arrow-button' ).length )
1563
- ) {
1564
- var $dropdown = $target.parents( '.fs-dropdown' ),
1565
- isActive = $dropdown.hasClass( 'active' );
1566
-
1567
- if ( ! isActive ) {
1568
- /**
1569
- * Close the other dropdown if it's active.
1570
- *
1571
- * @author Leo Fajardo (@leorw)
1572
- * @since 2.3.0
1573
- */
1574
- $( '.fs-dropdown.active' ).each( function() {
1575
- toggleDropdown( $( this ), false );
1576
- } );
1577
- }
1578
-
1579
- /**
1580
- * Toggle the current dropdown.
1581
- *
1582
- * @author Leo Fajardo (@leorw)
1583
- * @since 2.3.0
1584
- */
1585
- toggleDropdown( $dropdown, ! isActive );
1586
-
1587
- return true;
1588
- }
1589
-
1590
- /**
1591
- * Close all dropdowns.
1592
- *
1593
- * @author Leo Fajardo (@leorw)
1594
- * @since 2.3.0
1595
- */
1596
- toggleDropdown( $( this ).find( '.fs-dropdown' ), false );
1597
- });
1598
-
1599
- if ( 0 !== $dropdowns.length ) {
1600
- /**
1601
- * Add the `up` class so that the bottom dropdown's content will be shown above its buttons.
1602
- *
1603
- * @author Leo Fajardo (@leorw)
1604
- * @since 2.3.0
1605
- */
1606
- $( '#plugin-information-footer' ).find( '.fs-dropdown' ).addClass( 'up' );
1607
- }
1608
-
1609
- /**
1610
- * Returns the default state of the dropdown arrow button and hides the dropdown list.
1611
- *
1612
- * @author Leo Fajardo (@leorw)
1613
- * @since 2.3.0
1614
- *
1615
- * @param {Object} [$dropdown]
1616
- * @param {Boolean} [state]
1617
- */
1618
- function toggleDropdown( $dropdown, state ) {
1619
- if ( undef === $dropdown ) {
1620
- var $activeDropdown = $dropdowns.find( '.active' );
1621
- if ( 0 !== $activeDropdown.length ) {
1622
- $dropdown = $activeDropdown;
1623
- }
1624
- }
1625
-
1626
- if ( undef === $dropdown ) {
1627
- return;
1628
- }
1629
-
1630
- if ( undef === state ) {
1631
- state = false;
1632
- }
1633
-
1634
- $dropdown.toggleClass( 'active', state );
1635
- $dropdown.find( '.fs-dropdown-list' ).toggle( state );
1636
- $dropdown.find( '.fs-dropdown-arrow-button' ).toggleClass( 'active', state );
1637
- }
1638
- } )( jQuery );
1639
- </script>
1640
- <?php
1641
- iframe_footer();
1642
- exit;
1643
- }
1644
- }
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.0.6
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * Class FS_Plugin_Info_Dialog
15
+ *
16
+ * @author Vova Feldman (@svovaf)
17
+ * @since 1.1.7
18
+ */
19
+ class FS_Plugin_Info_Dialog {
20
+ /**
21
+ * @since 1.1.7
22
+ *
23
+ * @var FS_Logger
24
+ */
25
+ private $_logger;
26
+
27
+ /**
28
+ * @since 1.1.7
29
+ *
30
+ * @var Freemius
31
+ */
32
+ private $_fs;
33
+
34
+ /**
35
+ * Collection of plugin installation, update, download, activation, and purchase actions. This is used in
36
+ * populating the actions dropdown list when there are at least 2 actions. If there's only 1 action, a button
37
+ * is used instead.
38
+ *
39
+ * @author Leo Fajardo (@leorw)
40
+ * @since 2.3.0
41
+ *
42
+ * @var string[]
43
+ */
44
+ private $actions;
45
+
46
+ /**
47
+ * Contains plugin status information that is used to determine which actions should be part of the actions
48
+ * dropdown list.
49
+ *
50
+ * @author Leo Fajardo (@leorw)
51
+ * @since 2.3.0
52
+ *
53
+ * @var string[]
54
+ */
55
+ private $status;
56
+
57
+ function __construct( Freemius $fs ) {
58
+ $this->_fs = $fs;
59
+
60
+ $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
61
+
62
+ // Remove default plugin information action.
63
+ remove_all_actions( 'install_plugins_pre_plugin-information' );
64
+
65
+ // Override action with custom plugins function for add-ons.
66
+ add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) );
67
+
68
+ // Override request for plugin information for Add-ons.
69
+ add_filter(
70
+ 'fs_plugins_api',
71
+ array( &$this, '_get_addon_info_filter' ),
72
+ WP_FS__DEFAULT_PRIORITY, 3 );
73
+ }
74
+
75
+ /**
76
+ * Generate add-on plugin information.
77
+ *
78
+ * @author Vova Feldman (@svovaf)
79
+ * @since 1.0.6
80
+ *
81
+ * @param array $data
82
+ * @param string $action
83
+ * @param object|null $args
84
+ *
85
+ * @return array|null
86
+ */
87
+ function _get_addon_info_filter( $data, $action = '', $args = null ) {
88
+ $this->_logger->entrance();
89
+
90
+ $parent_plugin_id = fs_request_get( 'parent_plugin_id', $this->_fs->get_id() );
91
+
92
+ if ( $this->_fs->get_id() != $parent_plugin_id ||
93
+ ( 'plugin_information' !== $action ) ||
94
+ ! isset( $args->slug )
95
+ ) {
96
+ return $data;
97
+ }
98
+
99
+ // Find add-on by slug.
100
+ $selected_addon = $this->_fs->get_addon_by_slug( $args->slug, WP_FS__DEV_MODE );
101
+
102
+ if ( false === $selected_addon ) {
103
+ return $data;
104
+ }
105
+
106
+ if ( ! isset( $selected_addon->info ) ) {
107
+ // Setup some default info.
108
+ $selected_addon->info = new stdClass();
109
+ $selected_addon->info->selling_point_0 = 'Selling Point 1';
110
+ $selected_addon->info->selling_point_1 = 'Selling Point 2';
111
+ $selected_addon->info->selling_point_2 = 'Selling Point 3';
112
+ $selected_addon->info->description = '<p>Tell your users all about your add-on</p>';
113
+ }
114
+
115
+ fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
116
+
117
+ $data = $args;
118
+
119
+ $has_free_plan = false;
120
+ $has_paid_plan = false;
121
+
122
+ // Load add-on pricing.
123
+ $has_pricing = false;
124
+ $has_features = false;
125
+ $plans = false;
126
+
127
+ $result = $this->_fs->get_api_plugin_scope()->get( $this->_fs->add_show_pending( "/addons/{$selected_addon->id}/pricing.json?type=visible" ) );
128
+
129
+ if ( ! isset( $result->error ) ) {
130
+ $plans = $result->plans;
131
+
132
+ if ( is_array( $plans ) ) {
133
+ for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) {
134
+ $pricing = isset( $plans[ $i ]->pricing ) ? $plans[ $i ]->pricing : null;
135
+ $features = isset( $plans[ $i ]->features ) ? $plans[ $i ]->features : null;
136
+
137
+ $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] );
138
+ $plan = $plans[ $i ];
139
+
140
+ if ( 'free' == $plans[ $i ]->name ||
141
+ ! is_array( $pricing ) ||
142
+ 0 == count( $pricing )
143
+ ) {
144
+ $has_free_plan = true;
145
+ }
146
+
147
+ if ( is_array( $pricing ) && 0 < count( $pricing ) ) {
148
+ $filtered_pricing = array();
149
+
150
+ foreach ( $pricing as $prices ) {
151
+ $prices = new FS_Pricing( $prices );
152
+
153
+ if ( ! $prices->is_usd() ) {
154
+ /**
155
+ * Skip non-USD pricing.
156
+ *
157
+ * @author Leo Fajardo (@leorw)
158
+ * @since 2.3.1
159
+ */
160
+ continue;
161
+ }
162
+
163
+ if ( ( $prices->has_monthly() && $prices->monthly_price > 1.0 ) ||
164
+ ( $prices->has_annual() && $prices->annual_price > 1.0 ) ||
165
+ ( $prices->has_lifetime() && $prices->lifetime_price > 1.0 )
166
+ ) {
167
+ $filtered_pricing[] = $prices;
168
+ }
169
+ }
170
+
171
+ if ( ! empty( $filtered_pricing ) ) {
172
+ $has_paid_plan = true;
173
+
174
+ $plan->pricing = $filtered_pricing;
175
+
176
+ $has_pricing = true;
177
+ }
178
+ }
179
+
180
+ if ( is_array( $features ) && 0 < count( $features ) ) {
181
+ $plan->features = $features;
182
+
183
+ $has_features = true;
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ $latest = null;
190
+
191
+ if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) {
192
+ $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository(
193
+ 'plugin_information', (object) array(
194
+ 'slug' => $selected_addon->slug,
195
+ 'is_ssl' => is_ssl(),
196
+ 'fields' => array(
197
+ 'banners' => true,
198
+ 'reviews' => true,
199
+ 'downloaded' => false,
200
+ 'active_installs' => true
201
+ )
202
+ ) );
203
+
204
+ if ( ! empty( $repo_data ) ) {
205
+ $data = $repo_data;
206
+ $data->wp_org_missing = false;
207
+ } else {
208
+ // Couldn't find plugin on .org.
209
+ $selected_addon->is_wp_org_compliant = false;
210
+
211
+ // Plugin is missing, not on Freemius nor WP.org.
212
+ $data->wp_org_missing = true;
213
+ }
214
+
215
+ $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing );
216
+ } else {
217
+ $data->has_purchased_license = false;
218
+ $data->wp_org_missing = false;
219
+
220
+ $fs_addon = null;
221
+ $current_addon_version = false;
222
+ if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) {
223
+ $fs_addon = $this->_fs->get_addon_instance( $selected_addon->id );
224
+ $current_addon_version = $fs_addon->get_plugin_version();
225
+ } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) {
226
+ $addon_plugin_data = get_plugin_data(
227
+ ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ),
228
+ false,
229
+ false
230
+ );
231
+
232
+ if ( ! empty( $addon_plugin_data ) ) {
233
+ $current_addon_version = $addon_plugin_data['Version'];
234
+ }
235
+ }
236
+
237
+ // Fetch latest version from Freemius.
238
+ $latest = $this->_fs->_fetch_latest_version(
239
+ $selected_addon->id,
240
+ true,
241
+ WP_FS__TIME_24_HOURS_IN_SEC,
242
+ $current_addon_version
243
+ );
244
+
245
+ if ( $has_paid_plan ) {
246
+ $blog_id = fs_request_get( 'fs_blog_id' );
247
+ $has_valid_blog_id = is_numeric( $blog_id );
248
+
249
+ if ( $has_valid_blog_id ) {
250
+ switch_to_blog( $blog_id );
251
+ }
252
+
253
+ $data->checkout_link = $this->_fs->checkout_url(
254
+ WP_FS__PERIOD_ANNUALLY,
255
+ false,
256
+ array(),
257
+ ( $has_valid_blog_id ? false : null )
258
+ );
259
+
260
+ if ( $has_valid_blog_id ) {
261
+ restore_current_blog();
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Check if there's a purchased license in case the add-on can only be installed/downloaded as part of a purchased bundle.
267
+ *
268
+ * @author Leo Fajardo (@leorw)
269
+ * @since 2.4.1
270
+ */
271
+ if ( is_object( $fs_addon ) ) {
272
+ $data->has_purchased_license = $fs_addon->has_active_valid_license();
273
+ } else {
274
+ $account_addons = $this->_fs->get_account_addons();
275
+ if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) {
276
+ $data->has_purchased_license = true;
277
+ }
278
+ }
279
+
280
+ if ( $has_free_plan || $data->has_purchased_license ) {
281
+ $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id );
282
+ }
283
+
284
+ $data->fs_missing = (
285
+ false === $latest &&
286
+ (
287
+ empty( $selected_addon->premium_releases_count ) ||
288
+ ! ( $selected_addon->premium_releases_count > 0 )
289
+ )
290
+ );
291
+
292
+ // Fetch as much as possible info from local files.
293
+ $plugin_local_data = $this->_fs->get_plugin_data();
294
+ $data->author = $plugin_local_data['Author'];
295
+
296
+ if ( ! empty( $selected_addon->info->banner_url ) ) {
297
+ $data->banners = array(
298
+ 'low' => $selected_addon->info->banner_url,
299
+ );
300
+ }
301
+
302
+ if ( ! empty( $selected_addon->info->screenshots ) ) {
303
+ $view_vars = array(
304
+ 'screenshots' => $selected_addon->info->screenshots,
305
+ 'plugin' => $selected_addon,
306
+ );
307
+ $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars );
308
+ }
309
+
310
+ if ( is_object( $latest ) ) {
311
+ $data->version = $latest->version;
312
+ $data->last_updated = $latest->created;
313
+ $data->requires = $latest->requires_platform_version;
314
+ $data->tested = $latest->tested_up_to_version;
315
+ } else if ( ! empty( $current_addon_version ) ) {
316
+ $data->version = $current_addon_version;
317
+ } else {
318
+ // Add dummy version.
319
+ $data->version = '1.0.0';
320
+
321
+ // Add message to developer to deploy the plugin through Freemius.
322
+ }
323
+ }
324
+
325
+ $data->name = $selected_addon->title;
326
+ $view_vars = array( 'plugin' => $selected_addon );
327
+
328
+ if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) {
329
+ $latest_version_readme_data = $latest->readme;
330
+ if ( isset( $latest_version_readme_data->sections ) ) {
331
+ $data->sections = (array) $latest_version_readme_data->sections;
332
+ } else {
333
+ $data->sections = array();
334
+ }
335
+ }
336
+
337
+ $data->sections['description'] = fs_get_template( '/plugin-info/description.php', $view_vars );
338
+
339
+ if ( $has_pricing ) {
340
+ // Add plans to data.
341
+ $data->plans = $plans;
342
+
343
+ if ( $has_features ) {
344
+ $view_vars = array(
345
+ 'plans' => $plans,
346
+ 'plugin' => $selected_addon,
347
+ );
348
+ $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars );
349
+ }
350
+ }
351
+
352
+ $data->has_free_plan = $has_free_plan;
353
+ $data->has_paid_plan = $has_paid_plan;
354
+ $data->is_paid = $has_paid_plan;
355
+ $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant;
356
+ $data->premium_slug = $selected_addon->premium_slug;
357
+ $data->addon_id = $selected_addon->id;
358
+
359
+ if ( ! isset( $data->has_purchased_license ) ) {
360
+ $data->has_purchased_license = false;
361
+ }
362
+
363
+ return $data;
364
+ }
365
+
366
+ /**
367
+ * @author Vova Feldman (@svovaf)
368
+ * @since 1.1.7
369
+ *
370
+ * @param FS_Plugin_Plan $plan
371
+ *
372
+ * @return string
373
+ */
374
+ private function get_billing_cycle( FS_Plugin_Plan $plan ) {
375
+ $billing_cycle = null;
376
+
377
+ if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) {
378
+ $pricing = $plan->pricing[0];
379
+ if ( isset( $pricing->annual_price ) ) {
380
+ $billing_cycle = 'annual';
381
+ } else if ( isset( $pricing->monthly_price ) ) {
382
+ $billing_cycle = 'monthly';
383
+ } else if ( isset( $pricing->lifetime_price ) ) {
384
+ $billing_cycle = 'lifetime';
385
+ }
386
+ } else {
387
+ foreach ( $plan->pricing as $pricing ) {
388
+ if ( isset( $pricing->annual_price ) ) {
389
+ $billing_cycle = 'annual';
390
+ } else if ( isset( $pricing->monthly_price ) ) {
391
+ $billing_cycle = 'monthly';
392
+ } else if ( isset( $pricing->lifetime_price ) ) {
393
+ $billing_cycle = 'lifetime';
394
+ }
395
+
396
+ if ( ! is_null( $billing_cycle ) ) {
397
+ break;
398
+ }
399
+ }
400
+ }
401
+
402
+ return $billing_cycle;
403
+ }
404
+
405
+ /**
406
+ * @author Vova Feldman (@svovaf)
407
+ * @since 2.0.0
408
+ *
409
+ * @param FS_Plugin_Plan $plan
410
+ * @param FS_Pricing $pricing
411
+ *
412
+ * @return float|null|string
413
+ */
414
+ private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) {
415
+ $price_tag = '';
416
+ if ( isset( $pricing->annual_price ) ) {
417
+ $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' );
418
+ } else if ( isset( $pricing->monthly_price ) ) {
419
+ $price_tag = $pricing->monthly_price . ' / mo';
420
+ } else if ( isset( $pricing->lifetime_price ) ) {
421
+ $price_tag = $pricing->lifetime_price;
422
+ }
423
+
424
+ return '$' . $price_tag;
425
+ }
426
+
427
+ /**
428
+ * @author Leo Fajardo (@leorw)
429
+ * @since 2.3.0
430
+ *
431
+ * @param object $api
432
+ * @param FS_Plugin_Plan $plan
433
+ *
434
+ * @return string
435
+ */
436
+ private function get_actions_dropdown( $api, $plan = null ) {
437
+ $this->actions = isset( $this->actions ) ?
438
+ $this->actions :
439
+ $this->get_plugin_actions( $api );
440
+
441
+ $actions = $this->actions;
442
+
443
+ $checkout_cta = $this->get_checkout_cta( $api, $plan );
444
+ if ( ! empty( $checkout_cta ) ) {
445
+ /**
446
+ * If there's no license yet, make the checkout button the main CTA. Otherwise, make it the last item in
447
+ * the actions dropdown.
448
+ *
449
+ * @author Leo Fajardo (@leorw)
450
+ * @since 2.3.0
451
+ */
452
+ if ( ! $api->has_purchased_license ) {
453
+ array_unshift( $actions, $checkout_cta );
454
+ } else {
455
+ $actions[] = $checkout_cta;
456
+ }
457
+ }
458
+
459
+ if ( empty( $actions ) ) {
460
+ return '';
461
+ }
462
+
463
+ $total_actions = count( $actions );
464
+ if ( 1 === $total_actions ) {
465
+ return $actions[0];
466
+ }
467
+
468
+ ob_start();
469
+
470
+ ?>
471
+ <div class="fs-cta fs-dropdown">
472
+ <div class="button-group">
473
+ <?php
474
+ // This should NOT be sanitized as the $actions are HTML buttons already.
475
+ echo $actions[0] ?>
476
+ <div class="button button-primary fs-dropdown-arrow-button">
477
+ <span class="fs-dropdown-arrow"></span>
478
+ <ul class="fs-dropdown-list" style="display: none">
479
+ <?php for ( $i = 1; $i < $total_actions; $i ++ ) : ?>
480
+ <li><?php echo str_replace( 'button button-primary', '', $actions[ $i ] ) ?></li>
481
+ <?php endfor ?>
482
+ </ul>
483
+ </div>
484
+ </div>
485
+ </div>
486
+ <?php
487
+
488
+ return ob_get_clean();
489
+ }
490
+
491
+ /**
492
+ * @author Vova Feldman (@svovaf)
493
+ * @since 1.1.7
494
+ *
495
+ * @param object $api
496
+ * @param FS_Plugin_Plan $plan
497
+ *
498
+ * @return string
499
+ */
500
+ private function get_checkout_cta( $api, $plan = null ) {
501
+ if ( empty( $api->checkout_link ) ||
502
+ ! isset( $api->plans ) ||
503
+ ! is_array( $api->plans ) ||
504
+ 0 == count( $api->plans )
505
+ ) {
506
+ return '';
507
+ }
508
+
509
+ if ( is_null( $plan ) ) {
510
+ foreach ( $api->plans as $p ) {
511
+ if ( ! empty( $p->pricing ) ) {
512
+ $plan = $p;
513
+ break;
514
+ }
515
+ }
516
+ }
517
+
518
+ $blog_id = fs_request_get( 'fs_blog_id' );
519
+ $has_valid_blog_id = is_numeric( $blog_id );
520
+
521
+ if ( $has_valid_blog_id ) {
522
+ switch_to_blog( $blog_id );
523
+ }
524
+
525
+ $addon_checkout_url = $this->_fs->addon_checkout_url(
526
+ $plan->plugin_id,
527
+ $plan->pricing[0]->id,
528
+ $this->get_billing_cycle( $plan ),
529
+ $plan->has_trial(),
530
+ ( $has_valid_blog_id ? false : null )
531
+ );
532
+
533
+ if ( $has_valid_blog_id ) {
534
+ restore_current_blog();
535
+ }
536
+
537
+ return '<a class="button button-primary fs-checkout-button right" href="' . $addon_checkout_url . '" target="_parent">' .
538
+ esc_html( ! $plan->has_trial() ?
539
+ (
540
+ $api->has_purchased_license ?
541
+ fs_text_inline( 'Purchase More', 'purchase-more', $api->slug ) :
542
+ fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug )
543
+ ) :
544
+ sprintf(
545
+ /* translators: %s: N-days trial */
546
+ fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ),
547
+ $this->get_trial_period( $plan )
548
+ )
549
+ ) .
550
+ '</a>';
551
+ }
552
+
553
+ /**
554
+ * @author Leo Fajardo (@leorw)
555
+ * @since 2.3.0
556
+ *
557
+ * @param object $api
558
+ *
559
+ * @return string[]
560
+ */
561
+ private function get_plugin_actions( $api ) {
562
+ $this->status = isset( $this->status ) ?
563
+ $this->status :
564
+ install_plugin_install_status( $api );
565
+
566
+ $is_update_available = ( 'update_available' === $this->status['status'] );
567
+
568
+ if ( $is_update_available && empty( $this->status['url'] ) ) {
569
+ return array();
570
+ }
571
+
572
+ $blog_id = fs_request_get( 'fs_blog_id' );
573
+
574
+ $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $blog_id );
575
+
576
+ $actions = array();
577
+
578
+ $is_addon_activated = $this->_fs->is_addon_activated( $api->slug );
579
+ $fs_addon = null;
580
+
581
+ $is_free_installed = null;
582
+ $is_premium_installed = null;
583
+
584
+ $has_installed_version = ( 'install' !== $this->status['status'] );
585
+
586
+ if ( ! $api->has_paid_plan && ! $api->has_purchased_license ) {
587
+ /**
588
+ * Free-only add-on.
589
+ *
590
+ * @author Leo Fajardo (@leorw)
591
+ * @since 2.3.0
592
+ */
593
+ $is_free_installed = $has_installed_version;
594
+ $is_premium_installed = false;
595
+ } else if ( ! $api->has_free_plan ) {
596
+ /**
597
+ * Premium-only add-on.
598
+ *
599
+ * @author Leo Fajardo (@leorw)
600
+ * @since 2.3.0
601
+ */
602
+ $is_free_installed = false;
603
+ $is_premium_installed = $has_installed_version;
604
+ } else {
605
+ /**
606
+ * Freemium add-on.
607
+ *
608
+ * @author Leo Fajardo (@leorw)
609
+ * @since 2.3.0
610
+ */
611
+ if ( ! $has_installed_version ) {
612
+ $is_free_installed = false;
613
+ $is_premium_installed = false;
614
+ } else {
615
+ $fs_addon = $is_addon_activated ?
616
+ $this->_fs->get_addon_instance( $api->slug ) :
617
+ null;
618
+
619
+ if ( is_object( $fs_addon ) ) {
620
+ if ( $fs_addon->is_premium() ) {
621
+ $is_premium_installed = true;
622
+ } else {
623
+ $is_free_installed = true;
624
+ }
625
+ }
626
+
627
+ if ( is_null( $is_free_installed ) ) {
628
+ $is_free_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php" ) );
629
+ if ( ! $is_free_installed ) {
630
+ /**
631
+ * Check if there's a plugin installed in a directory named `$api->slug`.
632
+ *
633
+ * @author Leo Fajardo (@leorw)
634
+ * @since 2.3.0
635
+ */
636
+ $installed_plugins = get_plugins( '/' . $api->slug );
637
+ $is_free_installed = ( ! empty( $installed_plugins ) );
638
+ }
639
+ }
640
+
641
+ if ( is_null( $is_premium_installed ) ) {
642
+ $is_premium_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php" ) );
643
+ if ( ! $is_premium_installed ) {
644
+ /**
645
+ * Check if there's a plugin installed in a directory named `$api->premium_slug`.
646
+ *
647
+ * @author Leo Fajardo (@leorw)
648
+ * @since 2.3.0
649
+ */
650
+ $installed_plugins = get_plugins( '/' . $api->premium_slug );
651
+ $is_premium_installed = ( ! empty( $installed_plugins ) );
652
+ }
653
+ }
654
+ }
655
+
656
+ $has_installed_version = ( $is_free_installed || $is_premium_installed );
657
+ }
658
+
659
+ $this->status['is_free_installed'] = $is_free_installed;
660
+ $this->status['is_premium_installed'] = $is_premium_installed;
661
+
662
+ $can_install_free_version = false;
663
+ $can_install_free_version_update = false;
664
+ $can_download_free_version = false;
665
+ $can_activate_free_version = false;
666
+ $can_install_premium_version = false;
667
+ $can_install_premium_version_update = false;
668
+ $can_download_premium_version = false;
669
+ $can_activate_premium_version = false;
670
+
671
+ if ( ! $api->has_purchased_license ) {
672
+ if ( $api->has_free_plan ) {
673
+ if ( $has_installed_version ) {
674
+ if ( $is_update_available ) {
675
+ $can_install_free_version_update = true;
676
+ } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) {
677
+ $can_activate_free_version = true;
678
+ }
679
+ } else {
680
+ if (
681
+ $this->_fs->is_premium() ||
682
+ ! $this->_fs->is_org_repo_compliant() ||
683
+ $api->is_wp_org_compliant
684
+ ) {
685
+ $can_install_free_version = true;
686
+ } else {
687
+ $can_download_free_version = true;
688
+ }
689
+ }
690
+ }
691
+ } else {
692
+ if ( ! is_object( $fs_addon ) && $is_addon_activated ) {
693
+ $fs_addon = $this->_fs->get_addon_instance( $api->slug );
694
+ }
695
+
696
+ $can_download_premium_version = true;
697
+
698
+ if ( ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) {
699
+ if ( $is_premium_installed ) {
700
+ $can_activate_premium_version = ( ! $is_addon_activated || ! $fs_addon->is_premium() );
701
+ } else if ( $is_free_installed ) {
702
+ $can_activate_free_version = ( ! $is_addon_activated );
703
+ }
704
+ }
705
+
706
+ if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() ) {
707
+ if ( $is_update_available ) {
708
+ $can_install_premium_version_update = true;
709
+ } else if ( ! $is_premium_installed ) {
710
+ $can_install_premium_version = true;
711
+ }
712
+ }
713
+ }
714
+
715
+ if (
716
+ $can_install_premium_version ||
717
+ $can_install_premium_version_update
718
+ ) {
719
+ if ( is_numeric( $blog_id ) ) {
720
+ /**
721
+ * Replace the network status URL with a blog admin–based status URL if the `Add-Ons` page is loaded
722
+ * from a specific blog admin page (when `fs_blog_id` is valid) in order for plugin installation/update
723
+ * to work.
724
+ *
725
+ * @author Leo Fajardo (@leorw)
726
+ * @since 2.3.0
727
+ */
728
+ $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] );
729
+ }
730
+
731
+ /**
732
+ * Add the `fs_allow_updater_and_dialog` param to the install/update URL so that the add-on can be
733
+ * installed/updated.
734
+ *
735
+ * @author Leo Fajardo (@leorw)
736
+ * @since 2.3.0
737
+ */
738
+ $this->status['url'] = str_replace( '?', '?fs_allow_updater_and_dialog=true&amp;', $this->status['url'] );
739
+ }
740
+
741
+ if ( $can_install_free_version_update || $can_install_premium_version_update ) {
742
+ $actions[] = $this->get_cta(
743
+ ( $can_install_free_version_update ?
744
+ fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) :
745
+ fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ),
746
+ true,
747
+ false,
748
+ $this->status['url'],
749
+ '_parent'
750
+ );
751
+ } else if ( $can_install_free_version || $can_install_premium_version ) {
752
+ $actions[] = $this->get_cta(
753
+ ( $can_install_free_version ?
754
+ fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) :
755
+ fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ),
756
+ true,
757
+ false,
758
+ $this->status['url'],
759
+ '_parent'
760
+ );
761
+ }
762
+
763
+ $download_latest_action = '';
764
+
765
+ if (
766
+ ! empty( $api->download_link ) &&
767
+ ( $can_download_free_version || $can_download_premium_version )
768
+ ) {
769
+ $download_latest_action = $this->get_cta(
770
+ ( $can_download_free_version ?
771
+ fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) :
772
+ fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ),
773
+ true,
774
+ false,
775
+ esc_url( $api->download_link )
776
+ );
777
+ }
778
+
779
+ if ( ! $can_activate_free_version && ! $can_activate_premium_version ) {
780
+ if ( ! empty( $download_latest_action ) ) {
781
+ $actions[] = $download_latest_action;
782
+ }
783
+ } else {
784
+ $activate_action = sprintf(
785
+ '<a class="button button-primary edit" href="%s" title="%s" target="_parent">%s</a>',
786
+ wp_nonce_url( ( is_numeric( $blog_id ) ? trailingslashit( get_admin_url( $blog_id ) ) : '' ) . 'plugins.php?action=activate&amp;plugin=' . $this->status['file'], 'activate-plugin_' . $this->status['file'] ),
787
+ fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $api->slug ),
788
+ $can_activate_free_version ?
789
+ fs_text_inline( 'Activate Free Version', 'activate-free', $api->slug ) :
790
+ fs_text_inline( 'Activate', 'activate', $api->slug )
791
+ );
792
+
793
+ if ( ! $can_download_premium_version && ! empty( $download_latest_action ) ) {
794
+ $actions[] = $download_latest_action;
795
+
796
+ $download_latest_action = '';
797
+ }
798
+
799
+ if ( $can_install_premium_version || $can_install_premium_version_update ) {
800
+ if ( $can_download_premium_version && ! empty( $download_latest_action ) ) {
801
+ $actions[] = $download_latest_action;
802
+
803
+ $download_latest_action = '';
804
+ }
805
+
806
+ $actions[] = $activate_action;
807
+ } else {
808
+ array_unshift( $actions, $activate_action );
809
+ }
810
+
811
+ if ( ! empty ($download_latest_action ) ) {
812
+ $actions[] = $download_latest_action;
813
+ }
814
+ }
815
+
816
+ return $actions;
817
+ }
818
+
819
+ /**
820
+ * Rebuilds the status URL based on the admin URL.
821
+ *
822
+ * @author Leo Fajardo (@leorw)
823
+ * @since 2.3.0
824
+ *
825
+ * @param int $blog_id
826
+ * @param string $network_status_url
827
+ * @param string $status
828
+ *
829
+ * @return string
830
+ */
831
+ private static function get_blog_status_url( $blog_id, $network_status_url, $status ) {
832
+ if ( ! in_array( $status, array( 'install', 'update_available' ) ) ) {
833
+ return $network_status_url;
834
+ }
835
+
836
+ $action = ( 'install' === $status ) ?
837
+ 'install-plugin' :
838
+ 'upgrade-plugin';
839
+
840
+ $query = parse_url( $network_status_url, PHP_URL_QUERY );
841
+ if ( empty( $query ) ) {
842
+ return $network_status_url;
843
+ }
844
+
845
+ parse_str( html_entity_decode( $query ), $url_params );
846
+ if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) {
847
+ return $network_status_url;
848
+ }
849
+
850
+ $plugin = $url_params['plugin'];
851
+
852
+ return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}");
853
+ }
854
+
855
+ /**
856
+ * Helper method to get a CTA button HTML.
857
+ *
858
+ * @author Vova Feldman (@svovaf)
859
+ * @since 2.0.0
860
+ *
861
+ * @param string $label
862
+ * @param bool $is_primary
863
+ * @param bool $is_disabled
864
+ * @param string $href
865
+ * @param string $target
866
+ *
867
+ * @return string
868
+ */
869
+ private function get_cta(
870
+ $label,
871
+ $is_primary = true,
872
+ $is_disabled = false,
873
+ $href = '',
874
+ $target = '_blank'
875
+ ) {
876
+ $classes = array();
877
+
878
+ if ( ! $is_primary ) {
879
+ $classes[] = 'left';
880
+ } else {
881
+ $classes[] = 'button-primary';
882
+ $classes[] = 'right';
883
+ }
884
+
885
+ if ( $is_disabled ) {
886
+ $classes[] = 'disabled';
887
+ }
888
+
889
+ $rel = ( '_blank' === $target ) ? ' rel="noopener noreferrer"' : '';
890
+
891
+ return sprintf(
892
+ '<a %s class="button %s">%s</a>',
893
+ empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel,
894
+ implode( ' ', $classes ),
895
+ $label
896
+ );
897
+ }
898
+
899
+ /**
900
+ * @author Vova Feldman (@svovaf)
901
+ * @since 1.1.7
902
+ *
903
+ * @param FS_Plugin_Plan $plan
904
+ *
905
+ * @return string
906
+ */
907
+ private function get_trial_period( $plan ) {
908
+ $trial_period = (int) $plan->trial_period;
909
+
910
+ switch ( $trial_period ) {
911
+ case 30:
912
+ return 'month';
913
+ case 60:
914
+ return '2 months';
915
+ default:
916
+ return "{$plan->trial_period} days";
917
+ }
918
+ }
919
+
920
+ /**
921
+ * Display plugin information in dialog box form.
922
+ *
923
+ * Based on core install_plugin_information() function.
924
+ *
925
+ * @author Vova Feldman (@svovaf)
926
+ * @since 1.0.6
927
+ */
928
+ function install_plugin_information() {
929
+ global $tab;
930
+
931
+ if ( empty( $_REQUEST['plugin'] ) ) {
932
+ return;
933
+ }
934
+
935
+ $args = array(
936
+ 'slug' => wp_unslash( $_REQUEST['plugin'] ),
937
+ 'is_ssl' => is_ssl(),
938
+ 'fields' => array(
939
+ 'banners' => true,
940
+ 'reviews' => true,
941
+ 'downloaded' => false,
942
+ 'active_installs' => true
943
+ )
944
+ );
945
+
946
+ if ( is_array( $args ) ) {
947
+ $args = (object) $args;
948
+ }
949
+
950
+ if ( ! isset( $args->per_page ) ) {
951
+ $args->per_page = 24;
952
+ }
953
+
954
+ if ( ! isset( $args->locale ) ) {
955
+ $args->locale = get_locale();
956
+ }
957
+
958
+ $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args );
959
+
960
+ if ( is_wp_error( $api ) ) {
961
+ wp_die( $api );
962
+ }
963
+
964
+ $plugins_allowedtags = array(
965
+ 'a' => array(
966
+ 'href' => array(),
967
+ 'title' => array(),
968
+ 'target' => array(),
969
+ // Add image style for screenshots.
970
+ 'class' => array()
971
+ ),
972
+ 'style' => array(),
973
+ 'abbr' => array( 'title' => array() ),
974
+ 'acronym' => array( 'title' => array() ),
975
+ 'code' => array(),
976
+ 'pre' => array(),
977
+ 'em' => array(),
978
+ 'strong' => array(),
979
+ 'div' => array( 'class' => array() ),
980
+ 'span' => array( 'class' => array() ),
981
+ 'p' => array(),
982
+ 'ul' => array(),
983
+ 'ol' => array(),
984
+ 'li' => array( 'class' => array() ),
985
+ 'i' => array( 'class' => array() ),
986
+ 'h1' => array(),
987
+ 'h2' => array(),
988
+ 'h3' => array(),
989
+ 'h4' => array(),
990
+ 'h5' => array(),
991
+ 'h6' => array(),
992
+ 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ),
993
+ // 'table' => array(),
994
+ // 'td' => array(),
995
+ // 'tr' => array(),
996
+ // 'th' => array(),
997
+ // 'thead' => array(),
998
+ // 'tbody' => array(),
999
+ );
1000
+
1001
+ $plugins_section_titles = array(
1002
+ 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ),
1003
+ 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ),
1004
+ 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ),
1005
+ 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ),
1006
+ 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ),
1007
+ 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ),
1008
+ 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ),
1009
+ );
1010
+
1011
+ // Sanitize HTML
1012
+ // foreach ( (array) $api->sections as $section_name => $content ) {
1013
+ // $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags );
1014
+ // }
1015
+
1016
+ foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) {
1017
+ if ( isset( $api->$key ) ) {
1018
+ $api->$key = wp_kses( $api->$key, $plugins_allowedtags );
1019
+ }
1020
+ }
1021
+
1022
+ // Add after $api->slug is ready.
1023
+ $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug );
1024
+
1025
+ $_tab = esc_attr( $tab );
1026
+
1027
+ $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English.
1028
+ if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) {
1029
+ $section_titles = array_keys( (array) $api->sections );
1030
+ $section = array_shift( $section_titles );
1031
+ }
1032
+
1033
+ iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) );
1034
+
1035
+ $_with_banner = '';
1036
+
1037
+ // var_dump($api->banners);
1038
+ if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) {
1039
+ $_with_banner = 'with-banner';
1040
+ $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low'];
1041
+ $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high'];
1042
+ ?>
1043
+ <style type="text/css">
1044
+ #plugin-information-title.with-banner
1045
+ {
1046
+ background-image: url( <?php echo esc_url( $low ); ?> );
1047
+ }
1048
+
1049
+ @media only screen and ( -webkit-min-device-pixel-ratio: 1.5 )
1050
+ {
1051
+ #plugin-information-title.with-banner
1052
+ {
1053
+ background-image: url( <?php echo esc_url( $high ); ?> );
1054
+ }
1055
+ }
1056
+ </style>
1057
+ <?php
1058
+ }
1059
+
1060
+ echo '<div id="plugin-information-scrollable">';
1061
+ echo "<div id='{$_tab}-title' class='{$_with_banner}'><div class='vignette'></div><h2>{$api->name}</h2></div>";
1062
+ echo "<div id='{$_tab}-tabs' class='{$_with_banner}'>\n";
1063
+
1064
+ foreach ( (array) $api->sections as $section_name => $content ) {
1065
+ if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) {
1066
+ continue;
1067
+ }
1068
+
1069
+ if ( isset( $plugins_section_titles[ $section_name ] ) ) {
1070
+ $title = $plugins_section_titles[ $section_name ];
1071
+ } else {
1072
+ $title = ucwords( str_replace( '_', ' ', $section_name ) );
1073
+ }
1074
+
1075
+ $class = ( $section_name === $section ) ? ' class="current"' : '';
1076
+ $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) );
1077
+ $href = esc_url( $href );
1078
+ $san_section = esc_attr( $section_name );
1079
+ echo "\t<a name='$san_section' href='$href' $class>" . esc_html( $title ) . "</a>\n";
1080
+ }
1081
+
1082
+ echo "</div>\n";
1083
+
1084
+ ?>
1085
+ <div id="<?php echo $_tab; ?>-content" class='<?php echo $_with_banner; ?>'>
1086
+ <div class="fyi">
1087
+ <?php if ( $api->is_paid ) : ?>
1088
+ <?php if ( isset( $api->plans ) ) : ?>
1089
+ <div class="plugin-information-pricing">
1090
+ <?php foreach ( $api->plans as $plan ) : ?>
1091
+ <?php
1092
+ if ( empty( $plan->pricing ) ) {
1093
+ continue;
1094
+ }
1095
+
1096
+ /**
1097
+ * @var FS_Plugin_Plan $plan
1098
+ */
1099
+ ?>
1100
+ <?php $first_pricing = $plan->pricing[0] ?>
1101
+ <?php $is_multi_cycle = $first_pricing->is_multi_cycle() ?>
1102
+ <div class="fs-plan<?php if ( ! $is_multi_cycle ) {
1103
+ echo ' fs-single-cycle';
1104
+ } ?>" data-plan-id="<?php echo $plan->id ?>">
1105
+ <h3 data-plan="<?php echo $plan->id ?>"><?php echo esc_html( sprintf( fs_text_x_inline( '%s Plan', 'e.g. Professional Plan', 'x-plan', $api->slug ), $plan->title ) ) ?></h3>
1106
+ <?php $has_annual = $first_pricing->has_annual() ?>
1107
+ <?php $has_monthly = $first_pricing->has_monthly() ?>
1108
+ <div class="nav-tab-wrapper">
1109
+ <?php $billing_cycles = array( 'monthly', 'annual', 'lifetime' ) ?>
1110
+ <?php $i = 0;
1111
+ foreach ( $billing_cycles as $cycle ) : ?>
1112
+ <?php $prop = "{$cycle}_price";
1113
+ if ( isset( $first_pricing->{$prop} ) ) : ?>
1114
+ <?php $is_featured = ( 'annual' === $cycle && $is_multi_cycle ) ?>
1115
+ <?php
1116
+ $prices = array();
1117
+ foreach ( $plan->pricing as $pricing ) {
1118
+ if ( isset( $pricing->{$prop} ) ) {
1119
+ $prices[] = array(
1120
+ 'id' => $pricing->id,
1121
+ 'licenses' => $pricing->licenses,
1122
+ 'price' => $pricing->{$prop}
1123
+ );
1124
+ }
1125
+ }
1126
+ ?>
1127
+ <a class="nav-tab" data-billing-cycle="<?php echo $cycle ?>"
1128
+ data-pricing="<?php echo esc_attr( json_encode( $prices ) ) ?>">
1129
+ <?php if ( $is_featured ) : ?>
1130
+ <label>
1131
+ &#9733; <?php fs_esc_html_echo_x_inline( 'Best', 'e.g. the best product', 'best', $api->slug ) ?>
1132
+ &#9733;</label>
1133
+ <?php endif ?>
1134
+ <?php
1135
+ switch ( $cycle ) {
1136
+ case 'monthly':
1137
+ fs_esc_html_echo_x_inline( 'Monthly', 'as every month', 'monthly', $api->slug );
1138
+ break;
1139
+ case 'annual':
1140
+ fs_esc_html_echo_x_inline( 'Annual', 'as once a year', 'annual', $api->slug );
1141
+ break;
1142
+ case 'lifetime':
1143
+ fs_esc_html_echo_inline( 'Lifetime', 'lifetime', $api->slug );
1144
+ break;
1145
+ }
1146
+ ?>
1147
+ </a>
1148
+ <?php endif ?>
1149
+ <?php $i ++; endforeach ?>
1150
+ <?php wp_enqueue_script( 'jquery' ) ?>
1151
+ <script type="text/javascript">
1152
+ (function ($, undef) {
1153
+ var
1154
+ _formatBillingFrequency = function (cycle) {
1155
+ switch (cycle) {
1156
+ case 'monthly':
1157
+ return '<?php printf( fs_text_x_inline( 'Billed %s', 'e.g. billed monthly', 'billed-x', $api->slug ), fs_text_x_inline( 'Monthly', 'as every month', 'monthly', $api->slug ) ) ?>';
1158
+ case 'annual':
1159
+ return '<?php printf( fs_text_x_inline( 'Billed %s', 'e.g. billed monthly', 'billed-x', $api->slug ), fs_text_x_inline( 'Annually', 'as once a year', 'annually', $api->slug ) ) ?>';
1160
+ case 'lifetime':
1161
+ return '<?php printf( fs_text_x_inline( 'Billed %s', 'e.g. billed monthly', 'billed-x', $api->slug ), fs_text_x_inline( 'Once', 'as once a year', 'once', $api->slug ) ) ?>';
1162
+ }
1163
+ },
1164
+ _formatLicensesTitle = function (pricing) {
1165
+ switch (pricing.licenses) {
1166
+ case 1:
1167
+ return '<?php fs_esc_attr_echo_inline( 'Single Site License', 'license-single-site', $api->slug ) ?>';
1168
+ case null:
1169
+ return '<?php fs_esc_attr_echo_inline( 'Unlimited Licenses', 'license-unlimited', $api->slug ) ?>';
1170
+ default:
1171
+ return '<?php fs_esc_attr_echo_inline( 'Up to %s Sites', 'license-x-sites', $api->slug ) ?>'.replace('%s', pricing.licenses);
1172
+ }
1173
+ },
1174
+ _formatPrice = function (pricing, cycle, multipleLicenses) {
1175
+ if (undef === multipleLicenses)
1176
+ multipleLicenses = true;
1177
+
1178
+ var priceCycle;
1179
+ switch (cycle) {
1180
+ case 'monthly':
1181
+ priceCycle = ' / <?php fs_echo_x_inline( 'mo', 'as monthly period', 'mo', $api->slug ) ?>';
1182
+ break;
1183
+ case 'lifetime':
1184
+ priceCycle = '';
1185
+ break;
1186
+ case 'annual':
1187
+ default:
1188
+ priceCycle = ' / <?php fs_echo_x_inline( 'year', 'as annual period', 'year', $api->slug ) ?>';
1189
+ break;
1190
+ }
1191
+
1192
+ if (!multipleLicenses && 1 == pricing.licenses) {
1193
+ return '$' + pricing.price + priceCycle;
1194
+ }
1195
+
1196
+ return _formatLicensesTitle(pricing) + ' - <var class="fs-price">$' + pricing.price + priceCycle + '</var>';
1197
+ },
1198
+ _checkoutUrl = function (plan, pricing, cycle) {
1199
+ return '<?php echo esc_url_raw( remove_query_arg( 'billing_cycle', add_query_arg( array( 'plugin_id' => $plan->plugin_id ), $api->checkout_link ) ) ) ?>' +
1200
+ '&plan_id=' + plan +
1201
+ '&pricing_id=' + pricing +
1202
+ '&billing_cycle=' + cycle<?php if ( $plan->has_trial() ) {
1203
+ echo " + '&trial=true'";
1204
+ }?>;
1205
+ },
1206
+ _updateCtaUrl = function (plan, pricing, cycle) {
1207
+ $('.plugin-information-pricing .fs-checkout-button, #plugin-information-footer .fs-checkout-button').attr('href', _checkoutUrl(plan, pricing, cycle));
1208
+ };
1209
+
1210
+ $(document).ready(function () {
1211
+ var $plan = $('.plugin-information-pricing .fs-plan[data-plan-id=<?php echo $plan->id ?>]');
1212
+ $plan.find('input[type=radio]').on('click', function () {
1213
+ _updateCtaUrl(
1214
+ $plan.attr('data-plan-id'),
1215
+ $(this).val(),
1216
+ $plan.find('.nav-tab-active').attr('data-billing-cycle')
1217
+ );
1218
+
1219
+ $plan.find('.fs-trial-terms .fs-price').html(
1220
+ $(this).parents('label').find('.fs-price').html()
1221
+ );
1222
+ });
1223
+
1224
+ $plan.find('.nav-tab').click(function () {
1225
+ if ($(this).hasClass('nav-tab-active'))
1226
+ return;
1227
+
1228
+ var $this = $(this),
1229
+ billingCycle = $this.attr('data-billing-cycle'),
1230
+ pricing = JSON.parse($this.attr('data-pricing')),
1231
+ $pricesList = $this.parents('.fs-plan').find('.fs-pricing-body .fs-licenses'),
1232
+ html = '';
1233
+
1234
+ // Un-select previously selected tab.
1235
+ $plan.find('.nav-tab').removeClass('nav-tab-active');
1236
+
1237
+ // Select current tab.
1238
+ $this.addClass('nav-tab-active');
1239
+
1240
+ // Render licenses prices.
1241
+ if (1 == pricing.length) {
1242
+ html = '<li><label><?php echo fs_esc_attr_x_inline( 'Price', 'noun', 'price', $api->slug ) ?>: ' + _formatPrice(pricing[0], billingCycle, false) + '</label></li>';
1243
+ } else {
1244
+ for (var i = 0; i < pricing.length; i++) {
1245
+ html += '<li><label><input name="pricing-<?php echo $plan->id ?>" type="radio" value="' + pricing[i].id + '">' + _formatPrice(pricing[i], billingCycle) + '</label></li>';
1246
+ }
1247
+ }
1248
+ $pricesList.html(html);
1249
+
1250
+ if (1 < pricing.length) {
1251
+ // Select first license option.
1252
+ $pricesList.find('li:first input').click();
1253
+ }
1254
+ else {
1255
+ _updateCtaUrl(
1256
+ $plan.attr('data-plan-id'),
1257
+ pricing[0].id,
1258
+ billingCycle
1259
+ );
1260
+ }
1261
+
1262
+ // Update billing frequency.
1263
+ $plan.find('.fs-billing-frequency').html(_formatBillingFrequency(billingCycle));
1264
+
1265
+ if ('annual' === billingCycle) {
1266
+ $plan.find('.fs-annual-discount').show();
1267
+ } else {
1268
+ $plan.find('.fs-annual-discount').hide();
1269
+ }
1270
+ });
1271
+
1272
+ <?php if ( $has_annual ) : ?>
1273
+ // Select annual by default.
1274
+ $plan.find('.nav-tab[data-billing-cycle=annual]').click();
1275
+ <?php else : ?>
1276
+ // Select first tab.
1277
+ $plan.find('.nav-tab:first').click();
1278
+ <?php endif ?>
1279
+ });
1280
+ }(jQuery));
1281
+ </script>
1282
+ </div>
1283
+ <div class="fs-pricing-body">
1284
+ <span class="fs-billing-frequency"></span>
1285
+ <?php $annual_discount = ( $has_annual && $has_monthly ) ? $plan->pricing[0]->annual_discount_percentage() : 0 ?>
1286
+ <?php if ( $annual_discount > 0 ) : ?>
1287
+ <span
1288
+ class="fs-annual-discount"><?php printf(
1289
+ /* translators: %s: Discount (e.g. discount of $5 or 10%) */
1290
+ fs_esc_html_inline( 'Save %s', 'save-x', $api->slug ), $annual_discount . '%' ) ?></span>
1291
+ <?php endif ?>
1292
+ <ul class="fs-licenses">
1293
+ </ul>
1294
+ <?php echo $this->get_actions_dropdown( $api, $plan ) ?>
1295
+ <div style="clear:both"></div>
1296
+ <?php if ( $plan->has_trial() ) : ?>
1297
+ <?php $trial_period = $this->get_trial_period( $plan ) ?>
1298
+ <ul class="fs-trial-terms">
1299
+ <li>
1300
+ <i class="dashicons dashicons-yes"></i><?php echo esc_html( sprintf( fs_text_inline( 'No commitment for %s - cancel anytime', 'no-commitment-x', $api->slug ), $trial_period ) ) ?>
1301
+ </li>
1302
+ <li>
1303
+ <i class="dashicons dashicons-yes"></i><?php printf( esc_html( fs_text_inline( 'After your free %s, pay as little as %s', 'after-x-pay-as-little-y', $api->slug ) ), $trial_period, '<var class="fs-price">' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '</var>' ) ?>
1304
+ </li>
1305
+ </ul>
1306
+ <?php endif ?>
1307
+ </div>
1308
+ </div>
1309
+ </div>
1310
+ <?php endforeach ?>
1311
+ <?php endif ?>
1312
+ <?php endif ?>
1313
+ <div>
1314
+ <h3><?php fs_echo_inline( 'Details', 'details', $api->slug ) ?></h3>
1315
+ <ul>
1316
+ <?php if ( ! empty( $api->version ) ) { ?>
1317
+ <li>
1318
+ <strong><?php fs_esc_html_echo_x_inline( 'Version', 'product version', 'version', $api->slug ); ?>
1319
+ :</strong> <?php echo $api->version; ?></li>
1320
+ <?php
1321
+ }
1322
+ if ( ! empty( $api->author ) ) {
1323
+ ?>
1324
+ <li>
1325
+ <strong><?php fs_echo_x_inline( 'Author', 'as the plugin author', 'author', $api->slug ); ?>
1326
+ :</strong> <?php echo links_add_target( $api->author, '_blank' ); ?>
1327
+ </li>
1328
+ <?php
1329
+ }
1330
+ if ( ! empty( $api->last_updated ) ) {
1331
+ ?>
1332
+ <li><strong><?php fs_echo_inline( 'Last Updated', 'last-updated', $api->slug ); ?>
1333
+ :</strong> <span
1334
+ title="<?php echo $api->last_updated; ?>">
1335
+ <?php echo esc_html( sprintf(
1336
+ /* translators: %s: time period (e.g. "2 hours" ago) */
1337
+ fs_text_x_inline( '%s ago', 'x-ago', $api->slug ),
1338
+ human_time_diff( strtotime( $api->last_updated ) )
1339
+ ) ) ?>
1340
+ </span></li>
1341
+ <?php
1342
+ }
1343
+ if ( ! empty( $api->requires ) ) {
1344
+ ?>
1345
+ <li>
1346
+ <strong><?php fs_esc_html_echo_inline( 'Requires WordPress Version', 'requires-wordpress-version', $api->slug ) ?>
1347
+ :</strong> <?php echo esc_html( sprintf( fs_text_inline( '%s or higher', 'x-or-higher', $api->slug ), $api->requires ) ) ?>
1348
+ </li>
1349
+ <?php
1350
+ }
1351
+ if ( ! empty( $api->tested ) ) {
1352
+ ?>
1353
+ <li>
1354
+ <strong><?php fs_esc_html_echo_inline( 'Compatible up to', 'compatible-up-to', $api->slug ); ?>
1355
+ :</strong> <?php echo $api->tested; ?>
1356
+ </li>
1357
+ <?php
1358
+ }
1359
+ if ( ! empty( $api->downloaded ) ) {
1360
+ ?>
1361
+ <li>
1362
+ <strong><?php fs_esc_html_echo_inline( 'Downloaded', 'downloaded', $api->slug ) ?>
1363
+ :</strong> <?php echo esc_html( sprintf(
1364
+ ( ( 1 == $api->downloaded ) ?
1365
+ /* translators: %s: 1 or One (Number of times downloaded) */
1366
+ fs_text_inline( '%s time', 'x-time', $api->slug ) :
1367
+ /* translators: %s: Number of times downloaded */
1368
+ fs_text_inline( '%s times', 'x-times', $api->slug )
1369
+ ),
1370
+ number_format_i18n( $api->downloaded )
1371
+ ) ); ?>
1372
+ </li>
1373
+ <?php
1374
+ }
1375
+ if ( ! empty( $api->slug ) && true == $api->is_wp_org_compliant ) {
1376
+ ?>
1377
+ <li><a target="_blank"
1378
+ rel="noopener noreferrer"
1379
+ href="https://wordpress.org/plugins/<?php echo $api->slug; ?>/"><?php fs_esc_html_echo_inline( 'WordPress.org Plugin Page', 'wp-org-plugin-page', $api->slug ) ?>
1380
+ &#187;</a>
1381
+ </li>
1382
+ <?php
1383
+ }
1384
+ if ( ! empty( $api->homepage ) ) {
1385
+ ?>
1386
+ <li><a target="_blank"
1387
+ rel="noopener noreferrer"
1388
+ href="<?php echo esc_url( $api->homepage ); ?>"><?php fs_esc_html_echo_inline( 'Plugin Homepage', 'plugin-homepage', $api->slug ) ?>
1389
+ &#187;</a>
1390
+ </li>
1391
+ <?php
1392
+ }
1393
+ if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) {
1394
+ ?>
1395
+ <li><a target="_blank"
1396
+ rel="noopener noreferrer"
1397
+ href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_esc_html_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1398
+ &#187;</a>
1399
+ </li>
1400
+ <?php } ?>
1401
+ </ul>
1402
+ </div>
1403
+ <?php if ( ! empty( $api->rating ) ) { ?>
1404
+ <h3><?php fs_echo_inline( 'Average Rating', 'average-rating', $api->slug ); ?></h3>
1405
+ <?php wp_star_rating( array(
1406
+ 'rating' => $api->rating,
1407
+ 'type' => 'percent',
1408
+ 'number' => $api->num_ratings
1409
+ ) ); ?>
1410
+ <small>(<?php echo esc_html( sprintf(
1411
+ fs_text_inline( 'based on %s', 'based-on-x', $api->slug ),
1412
+ sprintf(
1413
+ ( ( 1 == $api->num_ratings ) ?
1414
+ /* translators: %s: 1 or One */
1415
+ fs_text_inline( '%s rating', 'x-rating', $api->slug ) :
1416
+ /* translators: %s: Number larger than 1 */
1417
+ fs_text_inline( '%s ratings', 'x-ratings', $api->slug )
1418
+ ),
1419
+ number_format_i18n( $api->num_ratings )
1420
+ ) ) ) ?>)
1421
+ </small>
1422
+ <?php
1423
+ }
1424
+
1425
+ if ( ! empty( $api->ratings ) && array_sum( (array) $api->ratings ) > 0 ) {
1426
+ foreach ( $api->ratings as $key => $ratecount ) {
1427
+ // Avoid div-by-zero.
1428
+ $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0;
1429
+ $stars_label = sprintf(
1430
+ ( ( 1 == $key ) ?
1431
+ /* translators: %s: 1 or One */
1432
+ fs_text_inline( '%s star', 'x-star', $api->slug ) :
1433
+ /* translators: %s: Number larger than 1 */
1434
+ fs_text_inline( '%s stars', 'x-stars', $api->slug )
1435
+ ),
1436
+ number_format_i18n( $key )
1437
+ );
1438
+ ?>
1439
+ <div class="counter-container">
1440
+ <span class="counter-label"><a
1441
+ href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>"
1442
+ target="_blank"
1443
+ rel="noopener noreferrer"
1444
+ title="<?php echo esc_attr( sprintf(
1445
+ /* translators: %s: # of stars (e.g. 5 stars) */
1446
+ fs_text_inline( 'Click to see reviews that provided a rating of %s', 'click-to-reviews', $api->slug ),
1447
+ $stars_label
1448
+ ) ) ?>"><?php echo $stars_label ?></a></span>
1449
+ <span class="counter-back">
1450
+ <span class="counter-bar" style="width: <?php echo absint(92 * $_rating); ?>px;"></span>
1451
+ </span>
1452
+ <span class="counter-count"><?php echo number_format_i18n( $ratecount ); ?></span>
1453
+ </div>
1454
+ <?php
1455
+ }
1456
+ }
1457
+ if ( ! empty( $api->contributors ) ) {
1458
+ ?>
1459
+ <h3><?php fs_echo_inline( 'Contributors', 'contributors', $api->slug ); ?></h3>
1460
+ <ul class="contributors">
1461
+ <?php
1462
+ foreach ( (array) $api->contributors as $contrib_username => $contrib_profile ) {
1463
+ if ( empty( $contrib_username ) && empty( $contrib_profile ) ) {
1464
+ continue;
1465
+ }
1466
+ if ( empty( $contrib_username ) ) {
1467
+ $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile );
1468
+ }
1469
+ $contrib_username = sanitize_user( $contrib_username );
1470
+ if ( empty( $contrib_profile ) ) {
1471
+ echo "<li><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</li>";
1472
+ } else {
1473
+ echo "<li><a href='{$contrib_profile}' target='_blank' rel='noopener noreferrer'><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</a></li>";
1474
+ }
1475
+ }
1476
+ ?>
1477
+ </ul>
1478
+ <?php if ( ! empty( $api->donate_link ) ) { ?>
1479
+ <a target="_blank"
1480
+ rel="noopener noreferrer"
1481
+ href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1482
+ &#187;</a>
1483
+ <?php } ?>
1484
+ <?php } ?>
1485
+ </div>
1486
+ <div id="section-holder" class="wrap">
1487
+ <?php
1488
+ if ( ! empty( $api->tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) {
1489
+ echo '<div class="notice notice-warning"><p>' . '<strong>' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ':</strong> ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '</p></div>';
1490
+ } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) {
1491
+ echo '<div class="notice notice-warning"><p>' . '<strong>' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ':</strong> ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '</p></div>';
1492
+ }
1493
+
1494
+ foreach ( (array) $api->sections as $section_name => $content ) {
1495
+ $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' );
1496
+ $content = links_add_target( $content, '_blank' );
1497
+
1498
+ $san_section = esc_attr( $section_name );
1499
+
1500
+ $display = ( $section_name === $section ) ? 'block' : 'none';
1501
+
1502
+ if ( 'description' === $section_name &&
1503
+ ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) ||
1504
+ ( ! $api->is_wp_org_compliant && $api->fs_missing ) )
1505
+ ) {
1506
+ $missing_notice = array(
1507
+ 'type' => 'error',
1508
+ 'id' => md5( microtime() ),
1509
+ 'message' => $api->is_paid ?
1510
+ fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) :
1511
+ fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ),
1512
+ );
1513
+ fs_require_template( 'admin-notice.php', $missing_notice );
1514
+ }
1515
+ echo "\t<div id='section-{$san_section}' class='section' style='display: {$display};'>\n";
1516
+ echo $content;
1517
+ echo "\t</div>\n";
1518
+ }
1519
+ echo "</div>\n";
1520
+ echo "</div>\n";
1521
+ echo "</div>\n"; // #plugin-information-scrollable
1522
+ echo "<div id='$tab-footer'>\n";
1523
+
1524
+ if (
1525
+ ! empty( $api->download_link ) &&
1526
+ ! empty( $this->status ) &&
1527
+ in_array( $this->status['status'], array( 'newer_installed', 'latest_installed' ) )
1528
+ ) {
1529
+ if ( 'newer_installed' === $this->status['status'] ) {
1530
+ echo $this->get_cta(
1531
+ ( $this->status['is_premium_installed'] ?
1532
+ esc_html( sprintf( fs_text_inline( 'Newer Version (%s) Installed', 'newer-installed', $api->slug ), $this->status['version'] ) ) :
1533
+ esc_html( sprintf( fs_text_inline( 'Newer Free Version (%s) Installed', 'newer-free-installed', $api->slug ), $this->status['version'] ) ) ),
1534
+ false,
1535
+ true
1536
+ );
1537
+ } else {
1538
+ echo $this->get_cta(
1539
+ ( $this->status['is_premium_installed'] ?
1540
+ fs_esc_html_inline( 'Latest Version Installed', 'latest-installed', $api->slug ) :
1541
+ fs_esc_html_inline( 'Latest Free Version Installed', 'latest-free-installed', $api->slug ) ),
1542
+ false,
1543
+ true
1544
+ );
1545
+ }
1546
+ }
1547
+
1548
+ echo $this->get_actions_dropdown( $api, null );
1549
+
1550
+ echo "</div>\n";
1551
+ ?>
1552
+ <script type="text/javascript">
1553
+ ( function( $, undef ) {
1554
+ var $dropdowns = $( '.fs-dropdown' );
1555
+
1556
+ $( '#plugin-information' )
1557
+ .click( function( evt ) {
1558
+ var $target = $( evt.target );
1559
+
1560
+ if (
1561
+ $target.hasClass( 'fs-dropdown-arrow-button' ) ||
1562
+ ( 0 !== $target.parents( '.fs-dropdown-arrow-button' ).length )
1563
+ ) {
1564
+ var $dropdown = $target.parents( '.fs-dropdown' ),
1565
+ isActive = $dropdown.hasClass( 'active' );
1566
+
1567
+ if ( ! isActive ) {
1568
+ /**
1569
+ * Close the other dropdown if it's active.
1570
+ *
1571
+ * @author Leo Fajardo (@leorw)
1572
+ * @since 2.3.0
1573
+ */
1574
+ $( '.fs-dropdown.active' ).each( function() {
1575
+ toggleDropdown( $( this ), false );
1576
+ } );
1577
+ }
1578
+
1579
+ /**
1580
+ * Toggle the current dropdown.
1581
+ *
1582
+ * @author Leo Fajardo (@leorw)
1583
+ * @since 2.3.0
1584
+ */
1585
+ toggleDropdown( $dropdown, ! isActive );
1586
+
1587
+ return true;
1588
+ }
1589
+
1590
+ /**
1591
+ * Close all dropdowns.
1592
+ *
1593
+ * @author Leo Fajardo (@leorw)
1594
+ * @since 2.3.0
1595
+ */
1596
+ toggleDropdown( $( this ).find( '.fs-dropdown' ), false );
1597
+ });
1598
+
1599
+ if ( 0 !== $dropdowns.length ) {
1600
+ /**
1601
+ * Add the `up` class so that the bottom dropdown's content will be shown above its buttons.
1602
+ *
1603
+ * @author Leo Fajardo (@leorw)
1604
+ * @since 2.3.0
1605
+ */
1606
+ $( '#plugin-information-footer' ).find( '.fs-dropdown' ).addClass( 'up' );
1607
+ }
1608
+
1609
+ /**
1610
+ * Returns the default state of the dropdown arrow button and hides the dropdown list.
1611
+ *
1612
+ * @author Leo Fajardo (@leorw)
1613
+ * @since 2.3.0
1614
+ *
1615
+ * @param {Object} [$dropdown]
1616
+ * @param {Boolean} [state]
1617
+ */
1618
+ function toggleDropdown( $dropdown, state ) {
1619
+ if ( undef === $dropdown ) {
1620
+ var $activeDropdown = $dropdowns.find( '.active' );
1621
+ if ( 0 !== $activeDropdown.length ) {
1622
+ $dropdown = $activeDropdown;
1623
+ }
1624
+ }
1625
+
1626
+ if ( undef === $dropdown ) {
1627
+ return;
1628
+ }
1629
+
1630
+ if ( undef === state ) {
1631
+ state = false;
1632
+ }
1633
+
1634
+ $dropdown.toggleClass( 'active', state );
1635
+ $dropdown.find( '.fs-dropdown-list' ).toggle( state );
1636
+ $dropdown.find( '.fs-dropdown-arrow-button' ).toggleClass( 'active', state );
1637
+ }
1638
+ } )( jQuery );
1639
+ </script>
1640
+ <?php
1641
+ iframe_footer();
1642
+ exit;
1643
+ }
1644
+ }
freemius/includes/i18n.php CHANGED
@@ -1,605 +1,605 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.1.4
7
- *
8
- * @deprecated This file is no longer in use. It's still in the project for backward compatibility.
9
- */
10
-
11
- if ( ! defined( 'ABSPATH' ) ) {
12
- exit;
13
- }
14
-
15
- require_once dirname( __FILE__ ) . '/l10n.php';
16
-
17
- /**
18
- * All strings can now be overridden.
19
- *
20
- * For example, if we want to override:
21
- * 'you-are-step-away' => 'You are just one step away - %s',
22
- *
23
- * We can use the filter:
24
- * fs_override_i18n( array(
25
- * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ),
26
- * 'skip' => __( 'Not today', '{your-text_domain}' ),
27
- * ), '{plugin_slug}' );
28
- *
29
- * Or with the Freemius instance:
30
- *
31
- * my_freemius->override_i18n( array(
32
- * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ),
33
- * 'skip' => __( 'Not today', '{your-text_domain}' ),
34
- * ) );
35
- */
36
- global $fs_text;
37
-
38
- $fs_text = array(
39
- 'account' => _fs_text( 'Account' ),
40
- 'addon' => _fs_text( 'Add-On' ),
41
- 'contact-us' => _fs_text( 'Contact Us' ),
42
- 'contact-support' => _fs_text( 'Contact Support' ),
43
- 'change-ownership' => _fs_text( 'Change Ownership' ),
44
- 'support' => _fs_text( 'Support' ),
45
- 'support-forum' => _fs_text( 'Support Forum' ),
46
- 'add-ons' => _fs_text( 'Add-Ons' ),
47
- 'upgrade' => _fs_x( 'Upgrade', 'verb' ),
48
- 'awesome' => _fs_text( 'Awesome' ),
49
- 'pricing' => _fs_x( 'Pricing', 'noun' ),
50
- 'price' => _fs_x( 'Price', 'noun' ),
51
- 'unlimited-updates' => _fs_text( 'Unlimited Updates' ),
52
- 'downgrade' => _fs_x( 'Downgrade', 'verb' ),
53
- 'cancel-subscription' => _fs_x( 'Cancel Subscription', 'verb' ),
54
- 'cancel-trial' => _fs_text( 'Cancel Trial' ),
55
- 'free-trial' => _fs_text( 'Free Trial' ),
56
- 'start-free-x' => _fs_text( 'Start my free %s' ),
57
- 'no-commitment-x' => _fs_text( 'No commitment for %s - cancel anytime' ),
58
- 'after-x-pay-as-little-y' => _fs_text( 'After your free %s, pay as little as %s' ),
59
- 'details' => _fs_text( 'Details' ),
60
- 'account-details' => _fs_text( 'Account Details' ),
61
- 'delete' => _fs_x( 'Delete', 'verb' ),
62
- 'show' => _fs_x( 'Show', 'verb' ),
63
- 'hide' => _fs_x( 'Hide', 'verb' ),
64
- 'edit' => _fs_x( 'Edit', 'verb' ),
65
- 'update' => _fs_x( 'Update', 'verb' ),
66
- 'date' => _fs_text( 'Date' ),
67
- 'amount' => _fs_text( 'Amount' ),
68
- 'invoice' => _fs_text( 'Invoice' ),
69
- 'billing' => _fs_text( 'Billing' ),
70
- 'payments' => _fs_text( 'Payments' ),
71
- 'delete-account' => _fs_text( 'Delete Account' ),
72
- 'dismiss' => _fs_x( 'Dismiss', 'as close a window' ),
73
- 'plan' => _fs_x( 'Plan', 'as product pricing plan' ),
74
- 'change-plan' => _fs_text( 'Change Plan' ),
75
- 'download-x-version' => _fs_x( 'Download %s Version', 'as download professional version' ),
76
- 'download-x-version-now' => _fs_x( 'Download %s version now', 'as download professional version now' ),
77
- 'download-latest' => _fs_x( 'Download Latest', 'as download latest version' ),
78
- 'you-have-x-license' => _fs_x( 'You have a %s license.', 'E.g. you have a professional license.' ),
79
- 'new' => _fs_text( 'New' ),
80
- 'free' => _fs_text( 'Free' ),
81
- 'trial' => _fs_x( 'Trial', 'as trial plan' ),
82
- 'start-trial' => _fs_x( 'Start Trial', 'as starting a trial plan' ),
83
- 'purchase' => _fs_x( 'Purchase', 'verb' ),
84
- 'purchase-license' => _fs_text( 'Purchase License' ),
85
- 'buy' => _fs_x( 'Buy', 'verb' ),
86
- 'buy-license' => _fs_text( 'Buy License' ),
87
- 'license-single-site' => _fs_text( 'Single Site License' ),
88
- 'license-unlimited' => _fs_text( 'Unlimited Licenses' ),
89
- 'license-x-sites' => _fs_text( 'Up to %s Sites' ),
90
- 'renew-license-now' => _fs_text( '%sRenew your license now%s to access version %s security & feature updates, and support.' ),
91
- 'ask-for-upgrade-email-address' => _fs_text( "Enter the email address you've used for the upgrade below and we will resend you the license key." ),
92
- 'x-plan' => _fs_x( '%s Plan', 'e.g. Professional Plan' ),
93
- 'you-are-step-away' => _fs_text( 'You are just one step away - %s' ),
94
- 'activate-x-now' => _fs_x( 'Complete "%s" Activation Now',
95
- '%s - plugin name. As complete "Jetpack" activation now' ),
96
- 'few-plugin-tweaks' => _fs_text( 'We made a few tweaks to the %s, %s' ),
97
- 'optin-x-now' => _fs_text( 'Opt in to make "%s" better!' ),
98
- 'error' => _fs_text( 'Error' ),
99
- 'failed-finding-main-path' => _fs_text( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.' ),
100
- 'learn-more' => _fs_text( 'Learn more' ),
101
- 'license_not_whitelabeled' => _fs_text( "Is this your client's site? %s if you wish to hide sensitive info like your billing address and invoices from the WP Admin."),
102
- 'license_whitelabeled' => _fs_text( 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your billing address and invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.'),
103
-
104
- #region Affiliation
105
- 'affiliation' => _fs_text( 'Affiliation' ),
106
- 'affiliate' => _fs_text( 'Affiliate' ),
107
- 'affiliate-tracking' => _fs_text( '%s tracking cookie after the first visit to maximize earnings potential.' ),
108
- 'renewals-commission' => _fs_text( 'Get commission for automated subscription renewals.' ),
109
- 'affiliate-application-accepted' => _fs_text( "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." ),
110
- 'affiliate-application-thank-you' => _fs_text( "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." ),
111
- 'affiliate-application-rejected' => _fs_text( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." ),
112
- 'affiliate-account-suspended' => _fs_text( 'Your affiliation account was temporarily suspended.' ),
113
- 'affiliate-account-blocked' => _fs_text( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.' ),
114
- 'become-an-ambassador' => _fs_text( 'Like the %s? Become our ambassador and earn cash ;-)' ),
115
- 'become-an-ambassador-admin-notice' => _fs_text( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!' ),
116
- 'refer-new-customers' => _fs_text( 'Refer new customers to our %s and earn %s commission on each successful sale you refer!' ),
117
- 'program-summary' => _fs_text( 'Program Summary' ),
118
- 'commission-on-new-license-purchase' => _fs_text( '%s commission when a customer purchases a new license.' ),
119
- 'unlimited-commissions' => _fs_text( 'Unlimited commissions.' ),
120
- 'minimum-payout-amount' => _fs_text( '%s minimum payout amount.' ),
121
- 'payouts-unit-and-processing' => _fs_text( 'Payouts are in USD and processed monthly via PayPal.' ),
122
- 'commission-payment' => _fs_text( 'As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.' ),
123
- 'become-an-affiliate' => _fs_text( 'Become an affiliate' ),
124
- 'apply-to-become-an-affiliate' => _fs_text( 'Apply to become an affiliate' ),
125
- 'full-name' => _fs_text( 'Full name' ),
126
- 'paypal-account-email-address' => _fs_text( 'PayPal account email address' ),
127
- 'promotion-methods' => _fs_text( 'Promotion methods' ),
128
- 'social-media' => _fs_text( 'Social media (Facebook, Twitter, etc.)' ),
129
- 'mobile-apps' => _fs_text( 'Mobile apps' ),
130
- 'statistics-information-field-label' => _fs_text( 'Website, email, and social media statistics (optional)' ),
131
- 'statistics-information-field-desc' => _fs_text( 'Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).' ),
132
- 'promotion-method-desc-field-label' => _fs_text( 'How will you promote us?' ),
133
- 'promotion-method-desc-field-desc' => _fs_text( 'Please provide details on how you intend to promote %s (please be as specific as possible).' ),
134
- 'domain-field-label' => _fs_text( 'Where are you going to promote the %s?' ),
135
- 'domain-field-desc' => _fs_text( 'Enter the domain of your website or other websites from where you plan to promote the %s.' ),
136
- 'extra-domain-fields-label' => _fs_text( 'Extra Domains' ),
137
- 'extra-domain-fields-desc' => _fs_text( 'Extra domains where you will be marketing the product from.' ),
138
- 'add-another-domain' => _fs_text( 'Add another domain' ),
139
- 'remove' => _fs_x( 'Remove', 'Remove domain' ),
140
- 'email-address-is-required' => _fs_text( 'Email address is required.' ),
141
- 'domain-is-required' => _fs_text( 'Domain is required.' ),
142
- 'invalid-domain' => _fs_text( 'Invalid domain' ),
143
- 'paypal-email-address-is-required' => _fs_text( 'PayPal email address is required.' ),
144
- 'processing' => _fs_text( 'Processing...' ),
145
- 'non-expiring' => _fs_text( 'Non-expiring' ),
146
- 'account-is-pending-activation' => _fs_text( 'Account is pending activation.' ),
147
- #endregion Affiliation
148
-
149
- #region Account
150
- 'expiration' => _fs_x( 'Expiration', 'as expiration date' ),
151
- 'license' => _fs_x( 'License', 'as software license' ),
152
- 'not-verified' => _fs_text( 'not verified' ),
153
- 'verify-email' => _fs_text( 'Verify Email' ),
154
- 'expires-in' => _fs_x( 'Expires in %s', 'e.g. expires in 2 months' ),
155
- 'renews-in' => _fs_x( 'Auto renews in %s', 'e.g. auto renews in 2 months' ),
156
- 'no-expiration' => _fs_text( 'No expiration' ),
157
- 'expired' => _fs_text( 'Expired' ),
158
- 'cancelled' => _fs_text( 'Cancelled' ),
159
- 'in-x' => _fs_x( 'In %s', 'e.g. In 2 hours' ),
160
- 'x-ago' => _fs_x( '%s ago', 'e.g. 2 min ago' ),
161
- /* translators: %s: Version number (e.g. 4.6 or higher) */
162
- 'x-or-higher' => _fs_text( '%s or higher' ),
163
- 'version' => _fs_x( 'Version', 'as plugin version' ),
164
- 'name' => _fs_text( 'Name' ),
165
- 'email' => _fs_text( 'Email' ),
166
- 'email-address' => _fs_text( 'Email address' ),
167
- 'verified' => _fs_text( 'Verified' ),
168
- 'module' => _fs_text( 'Module' ),
169
- 'module-type' => _fs_text( 'Module Type' ),
170
- 'plugin' => _fs_text( 'Plugin' ),
171
- 'plugins' => _fs_text( 'Plugins' ),
172
- 'theme' => _fs_text( 'Theme' ),
173
- 'themes' => _fs_text( 'Themes' ),
174
- 'path' => _fs_x( 'Path', 'as file/folder path' ),
175
- 'title' => _fs_text( 'Title' ),
176
- 'free-version' => _fs_text( 'Free version' ),
177
- 'premium-version' => _fs_text( 'Premium version' ),
178
- 'slug' => _fs_x( 'Slug', 'as WP plugin slug' ),
179
- 'id' => _fs_text( 'ID' ),
180
- 'users' => _fs_text( 'Users' ),
181
- 'module-installs' => _fs_text( '%s Installs' ),
182
- 'sites' => _fs_x( 'Sites', 'like websites' ),
183
- 'user-id' => _fs_text( 'User ID' ),
184
- 'site-id' => _fs_text( 'Site ID' ),
185
- 'public-key' => _fs_text( 'Public Key' ),
186
- 'secret-key' => _fs_text( 'Secret Key' ),
187
- 'no-secret' => _fs_x( 'No Secret', 'as secret encryption key missing' ),
188
- 'no-id' => _fs_text( 'No ID' ),
189
- 'sync-license' => _fs_x( 'Sync License', 'as synchronize license' ),
190
- 'sync' => _fs_x( 'Sync', 'as synchronize' ),
191
- 'activate-license' => _fs_text( 'Activate License' ),
192
- 'activate-free-version' => _fs_text( 'Activate Free Version' ),
193
- 'activate-license-message' => _fs_text( 'Please enter the license key that you received in the email right after the purchase:' ),
194
- 'activating-license' => _fs_text( 'Activating license...' ),
195
- 'change-license' => _fs_text( 'Change License' ),
196
- 'update-license' => _fs_text( 'Update License' ),
197
- 'deactivate-license' => _fs_text( 'Deactivate License' ),
198
- 'activate' => _fs_text( 'Activate' ),
199
- 'deactivate' => _fs_text( 'Deactivate' ),
200
- 'skip-deactivate' => _fs_text( 'Skip & Deactivate' ),
201
- 'skip-and-x' => _fs_text( 'Skip & %s' ),
202
- 'no-deactivate' => _fs_text( 'No - just deactivate' ),
203
- 'yes-do-your-thing' => _fs_text( 'Yes - do your thing' ),
204
- 'active' => _fs_x( 'Active', 'active mode' ),
205
- 'is-active' => _fs_x( 'Is Active', 'is active mode?' ),
206
- 'install-now' => _fs_text( 'Install Now' ),
207
- 'install-update-now' => _fs_text( 'Install Update Now' ),
208
- 'more-information-about-x' => _fs_text( 'More information about %s' ),
209
- 'localhost' => _fs_text( 'Localhost' ),
210
- 'activate-x-plan' => _fs_x( 'Activate %s Plan', 'as activate Professional plan' ),
211
- 'x-left' => _fs_x( '%s left', 'as 5 licenses left' ),
212
- 'last-license' => _fs_text( 'Last license' ),
213
- 'what-is-your-x' => _fs_text( 'What is your %s?' ),
214
- 'activate-this-addon' => _fs_text( 'Activate this add-on' ),
215
- 'deactivate-license-confirm' => _fs_text( 'Deactivating your license will block all premium features, but will enable you to activate the license on another site. Are you sure you want to proceed?' ),
216
- 'delete-account-x-confirm' => _fs_text( 'Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the "Cancel" button, and first "Downgrade" your account. Are you sure you would like to continue with the deletion?' ),
217
- 'delete-account-confirm' => _fs_text( 'Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?' ),
218
- 'downgrade-x-confirm' => _fs_text( 'Downgrading your plan will immediately stop all future recurring payments and your %s plan license will expire in %s.' ),
219
- 'cancel-trial-confirm' => _fs_text( 'Cancelling the trial will immediately block access to all premium features. Are you sure?' ),
220
- 'after-downgrade-non-blocking' => _fs_text( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.' ),
221
- 'after-downgrade-blocking' => _fs_text( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.' ),
222
- 'proceed-confirmation' => _fs_text( 'Are you sure you want to proceed?' ),
223
- #endregion Account
224
-
225
- 'add-ons-for-x' => _fs_text( 'Add Ons for %s' ),
226
- 'add-ons-missing' => _fs_text( 'We could\'nt load the add-ons list. It\'s probably an issue on our side, please try to come back in few minutes.' ),
227
- #region Plugin Deactivation
228
- 'anonymous-feedback' => _fs_text( 'Anonymous feedback' ),
229
- 'quick-feedback' => _fs_text( 'Quick feedback' ),
230
- 'deactivation-share-reason' => _fs_text( 'If you have a moment, please let us know why you are %s' ),
231
- 'deactivating' => _fs_text( 'deactivating' ),
232
- 'deactivation' => _fs_text( 'Deactivation' ),
233
- 'theme-switch' => _fs_text( 'Theme Switch' ),
234
- 'switching' => _fs_text( 'switching' ),
235
- 'switch' => _fs_text( 'Switch' ),
236
- 'activate-x' => _fs_text( 'Activate %s' ),
237
- 'deactivation-modal-button-confirm' => _fs_text( 'Yes - %s' ),
238
- 'deactivation-modal-button-submit' => _fs_text( 'Submit & %s' ),
239
- 'cancel' => _fs_text( 'Cancel' ),
240
- 'reason-no-longer-needed' => _fs_text( 'I no longer need the %s' ),
241
- 'reason-found-a-better-plugin' => _fs_text( 'I found a better %s' ),
242
- 'reason-needed-for-a-short-period' => _fs_text( 'I only needed the %s for a short period' ),
243
- 'reason-broke-my-site' => _fs_text( 'The %s broke my site' ),
244
- 'reason-suddenly-stopped-working' => _fs_text( 'The %s suddenly stopped working' ),
245
- 'reason-cant-pay-anymore' => _fs_text( "I can't pay for it anymore" ),
246
- 'reason-temporary-deactivation' => _fs_text( "It's a temporary deactivation. I'm just debugging an issue." ),
247
- 'reason-temporary-x' => _fs_text( "It's a temporary %s. I'm just debugging an issue." ),
248
- 'reason-other' => _fs_x( 'Other',
249
- 'the text of the "other" reason for deactivating the module that is shown in the modal box.' ),
250
- 'ask-for-reason-message' => _fs_text( 'Kindly tell us the reason so we can improve.' ),
251
- 'placeholder-plugin-name' => _fs_text( "What's the %s's name?" ),
252
- 'placeholder-comfortable-price' => _fs_text( 'What price would you feel comfortable paying?' ),
253
- 'reason-couldnt-make-it-work' => _fs_text( "I couldn't understand how to make it work" ),
254
- 'reason-great-but-need-specific-feature' => _fs_text( "The %s is great, but I need specific feature that you don't support" ),
255
- 'reason-not-working' => _fs_text( 'The %s is not working' ),
256
- 'reason-not-what-i-was-looking-for' => _fs_text( "It's not what I was looking for" ),
257
- 'reason-didnt-work-as-expected' => _fs_text( "The %s didn't work as expected" ),
258
- 'placeholder-feature' => _fs_text( 'What feature?' ),
259
- 'placeholder-share-what-didnt-work' => _fs_text( "Kindly share what didn't work so we can fix it for future users..." ),
260
- 'placeholder-what-youve-been-looking-for' => _fs_text( "What you've been looking for?" ),
261
- 'placeholder-what-did-you-expect' => _fs_text( "What did you expect?" ),
262
- 'reason-didnt-work' => _fs_text( "The %s didn't work" ),
263
- 'reason-dont-like-to-share-my-information' => _fs_text( "I don't like to share my information with you" ),
264
- 'dont-have-to-share-any-data' => _fs_text( "You might have missed it, but you don't have to share any data and can just %s the opt-in." ),
265
- #endregion Plugin Deactivation
266
-
267
- #region Connect
268
- 'hey-x' => _fs_x( 'Hey %s,', 'greeting' ),
269
- 'thanks-x' => _fs_x( 'Thanks %s!', 'a greeting. E.g. Thanks John!' ),
270
- 'connect-message' => _fs_text( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.' ),
271
- 'connect-message_on-update' => _fs_text( 'Please help us improve %1$s! If you opt in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.' ),
272
- 'pending-activation-message' => _fs_text( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.' ),
273
- 'complete-the-install' => _fs_text( 'complete the install' ),
274
- 'start-the-trial' => _fs_text( 'start the trial' ),
275
- 'thanks-for-purchasing' => _fs_text( 'Thanks for purchasing %s! To get started, please enter your license key:' ),
276
- 'license-sync-disclaimer' => _fs_text( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.' ),
277
- 'what-permissions' => _fs_text( 'What permissions are being granted?' ),
278
- 'permissions-profile' => _fs_text( 'Your Profile Overview' ),
279
- 'permissions-profile_desc' => _fs_text( 'Name and email address' ),
280
- 'permissions-site' => _fs_text( 'Your Site Overview' ),
281
- 'permissions-site_desc' => _fs_text( 'Site URL, WP version, PHP info, plugins & themes' ),
282
- 'permissions-events' => _fs_text( 'Current %s Events' ),
283
- 'permissions-events_desc' => _fs_text( 'Activation, deactivation and uninstall' ),
284
- 'permissions-plugins_themes' => _fs_text( 'Plugins & Themes' ),
285
- 'permissions-plugins_themes_desc' => _fs_text( 'Titles, versions and state.' ),
286
- 'permissions-admin-notices' => _fs_text( 'Admin Notices' ),
287
- 'permissions-newsletter' => _fs_text( 'Newsletter' ),
288
- 'permissions-newsletter_desc' => _fs_text( 'Updates, announcements, marketing, no spam' ),
289
- 'privacy-policy' => _fs_text( 'Privacy Policy' ),
290
- 'tos' => _fs_text( 'Terms of Service' ),
291
- 'activating' => _fs_x( 'Activating', 'as activating plugin' ),
292
- 'sending-email' => _fs_x( 'Sending email', 'as in the process of sending an email' ),
293
- 'opt-in-connect' => _fs_x( 'Allow & Continue', 'button label' ),
294
- 'agree-activate-license' => _fs_x( 'Agree & Activate License', 'button label' ),
295
- 'skip' => _fs_x( 'Skip', 'verb' ),
296
- 'click-here-to-use-plugin-anonymously' => _fs_text( 'Click here to use the plugin anonymously' ),
297
- 'resend-activation-email' => _fs_text( 'Re-send activation email' ),
298
- 'license-key' => _fs_text( 'License key' ),
299
- 'send-license-key' => _fs_text( 'Send License Key' ),
300
- 'sending-license-key' => _fs_text( 'Sending license key' ),
301
- 'have-license-key' => _fs_text( 'Have a license key?' ),
302
- 'dont-have-license-key' => _fs_text( 'Don\'t have a license key?' ),
303
- 'cant-find-license-key' => _fs_text( "Can't find your license key?" ),
304
- 'email-not-found' => _fs_text( "We couldn't find your email address in the system, are you sure it's the right address?" ),
305
- 'no-active-licenses' => _fs_text( "We can't see any active licenses associated with that email address, are you sure it's the right address?" ),
306
- 'opt-in' => _fs_text( 'Opt In' ),
307
- 'opt-out' => _fs_text( 'Opt Out' ),
308
- 'opt-out-cancel' => _fs_text( 'On second thought - I want to continue helping' ),
309
- 'opting-out' => _fs_text( 'Opting out...' ),
310
- 'opting-in' => _fs_text( 'Opting in...' ),
311
- 'opt-out-message-appreciation' => _fs_text( 'We appreciate your help in making the %s better by letting us track some usage data.' ),
312
- 'opt-out-message-usage-tracking' => _fs_text( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." ),
313
- 'opt-out-message-clicking-opt-out' => _fs_text( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.' ),
314
- 'apply-on-all-sites-in-the-network' => _fs_text( 'Apply on all sites in the network.' ),
315
- 'delegate-to-site-admins' => _fs_text( 'Delegate to Site Admins' ),
316
- 'delegate-to-site-admins-and-continue' => _fs_text( 'Delegate to Site Admins & Continue' ),
317
- 'continue' => _fs_text( 'Continue' ),
318
- 'allow' => _fs_text( 'allow' ),
319
- 'delegate' => _fs_text( 'delegate' ),
320
- #endregion Connect
321
-
322
- #region Screenshots
323
- 'screenshots' => _fs_text( 'Screenshots' ),
324
- 'view-full-size-x' => _fs_text( 'Click to view full-size screenshot %d' ),
325
- #endregion Screenshots
326
-
327
- #region Debug
328
- 'freemius-debug' => _fs_text( 'Freemius Debug' ),
329
- 'on' => _fs_x( 'On', 'as turned on' ),
330
- 'off' => _fs_x( 'Off', 'as turned off' ),
331
- 'debugging' => _fs_x( 'Debugging', 'as code debugging' ),
332
- 'freemius-state' => _fs_text( 'Freemius State' ),
333
- 'connected' => _fs_x( 'Connected', 'as connection was successful' ),
334
- 'blocked' => _fs_x( 'Blocked', 'as connection blocked' ),
335
- 'api' => _fs_x( 'API', 'as application program interface' ),
336
- 'sdk' => _fs_x( 'SDK', 'as software development kit versions' ),
337
- 'sdk-versions' => _fs_x( 'SDK Versions', 'as software development kit versions' ),
338
- 'plugin-path' => _fs_x( 'Plugin Path', 'as plugin folder path' ),
339
- 'sdk-path' => _fs_x( 'SDK Path', 'as sdk path' ),
340
- 'addons-of-x' => _fs_text( 'Add Ons of Plugin %s' ),
341
- 'delete-all-confirm' => _fs_text( 'Are you sure you want to delete all Freemius data?' ),
342
- 'actions' => _fs_text( 'Actions' ),
343
- 'delete-all-accounts' => _fs_text( 'Delete All Accounts' ),
344
- 'start-fresh' => _fs_text( 'Start Fresh' ),
345
- 'clear-api-cache' => _fs_text( 'Clear API Cache' ),
346
- 'sync-data-from-server' => _fs_text( 'Sync Data From Server' ),
347
- 'scheduled-crons' => _fs_text( 'Scheduled Crons' ),
348
- 'cron-type' => _fs_text( 'Cron Type' ),
349
- 'plugins-themes-sync' => _fs_text( 'Plugins & Themes Sync' ),
350
- 'module-licenses' => _fs_text( '%s Licenses' ),
351
- 'debug-log' => _fs_text( 'Debug Log' ),
352
- 'all' => _fs_text( 'All' ),
353
- 'file' => _fs_text( 'File' ),
354
- 'function' => _fs_text( 'Function' ),
355
- 'process-id' => _fs_text( 'Process ID' ),
356
- 'logger' => _fs_text( 'Logger' ),
357
- 'message' => _fs_text( 'Message' ),
358
- 'download' => _fs_text( 'Download' ),
359
- 'filter' => _fs_text( 'Filter' ),
360
- 'type' => _fs_text( 'Type' ),
361
- 'all-types' => _fs_text( 'All Types' ),
362
- 'all-requests' => _fs_text( 'All Requests' ),
363
- #endregion Debug
364
-
365
- #region Expressions
366
- 'congrats' => _fs_x( 'Congrats', 'as congratulations' ),
367
- 'oops' => _fs_x( 'Oops', 'exclamation' ),
368
- 'yee-haw' => _fs_x( 'Yee-haw', 'interjection expressing joy or exuberance' ),
369
- 'woot' => _fs_x( 'W00t',
370
- '(especially in electronic communication) used to express elation, enthusiasm, or triumph.' ),
371
- 'right-on' => _fs_x( 'Right on', 'a positive response' ),
372
- 'hmm' => _fs_x( 'Hmm',
373
- 'something somebody says when they are thinking about what you have just said. ' ),
374
- 'ok' => _fs_text( 'O.K' ),
375
- 'hey' => _fs_x( 'Hey', 'exclamation' ),
376
- 'heads-up' => _fs_x( 'Heads up',
377
- 'advance notice of something that will need attention.' ),
378
- #endregion Expressions
379
-
380
- #region Admin Notices
381
- 'you-have-latest' => _fs_text( 'Seems like you got the latest release.' ),
382
- 'you-are-good' => _fs_text( 'You are all good!' ),
383
- 'user-exist-message' => _fs_text( 'Sorry, we could not complete the email update. Another user with the same email is already registered.' ),
384
- 'user-exist-message_ownership' => _fs_text( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.' ),
385
- 'email-updated-message' => _fs_text( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.' ),
386
- 'name-updated-message' => _fs_text( 'Your name was successfully updated.' ),
387
- 'x-updated' => _fs_text( 'You have successfully updated your %s.' ),
388
- 'name-update-failed-message' => _fs_text( 'Please provide your full name.' ),
389
- 'verification-email-sent-message' => _fs_text( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.' ),
390
- 'addons-info-external-message' => _fs_text( 'Just letting you know that the add-ons information of %s is being pulled from an external server.' ),
391
- 'no-cc-required' => _fs_text( 'No credit card required' ),
392
- 'premium-activated-message' => _fs_text( 'Premium %s version was successfully activated.' ),
393
- 'successful-version-upgrade-message' => _fs_text( 'The upgrade of %s was successfully completed.' ),
394
- 'activation-with-plan-x-message' => _fs_text( 'Your account was successfully activated with the %s plan.' ),
395
- 'download-latest-x-version-now' => _fs_text( 'Download the latest %s version now' ),
396
- 'follow-steps-to-complete-upgrade' => _fs_text( 'Please follow these steps to complete the upgrade' ),
397
- 'download-latest-x-version' => _fs_text( 'Download the latest %s version' ),
398
- 'download-latest-version' => _fs_text( 'Download the latest version' ),
399
- 'deactivate-free-version' => _fs_text( 'Deactivate the free version' ),
400
- 'upload-and-activate' => _fs_text( 'Upload and activate the downloaded version' ),
401
- 'howto-upload-activate' => _fs_text( 'How to upload and activate?' ),
402
- 'addon-successfully-purchased-message' => _fs_x( '%s Add-on was successfully purchased.',
403
- '%s - product name, e.g. Facebook add-on was successfully...' ),
404
- 'addon-successfully-upgraded-message' => _fs_text( 'Your %s Add-on plan was successfully upgraded.' ),
405
- 'email-verified-message' => _fs_text( 'Your email has been successfully verified - you are AWESOME!' ),
406
- 'plan-upgraded-message' => _fs_text( 'Your plan was successfully upgraded.' ),
407
- 'plan-changed-to-x-message' => _fs_text( 'Your plan was successfully changed to %s.' ),
408
- 'license-expired-blocking-message' => _fs_text( 'Your license has expired. You can still continue using the free %s forever.' ),
409
- 'license-cancelled' => _fs_text( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.' ),
410
- 'trial-started-message' => _fs_text( 'Your trial has been successfully started.' ),
411
- 'license-activated-message' => _fs_text( 'Your license was successfully activated.' ),
412
- 'no-active-license-message' => _fs_text( 'It looks like your site currently doesn\'t have an active license.' ),
413
- 'license-deactivation-message' => _fs_text( 'Your license was successfully deactivated, you are back to the %s plan.' ),
414
- 'license-deactivation-failed-message' => _fs_text( 'It looks like the license deactivation failed.' ),
415
- 'license-activation-failed-message' => _fs_text( 'It looks like the license could not be activated.' ),
416
- 'server-error-message' => _fs_text( 'Error received from the server:' ),
417
- 'trial-expired-message' => _fs_text( 'Your trial has expired. You can still continue using all our free features.' ),
418
- 'plan-x-downgraded-message' => _fs_text( 'Your plan was successfully downgraded. Your %s plan license will expire in %s.' ),
419
- 'plan-downgraded-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your plan downgrade. Please try again in few minutes.' ),
420
- 'trial-cancel-no-trial-message' => _fs_text( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)' ),
421
- 'trial-cancel-message' => _fs_text( 'Your %s free trial was successfully cancelled.' ),
422
- 'version-x-released' => _fs_x( 'Version %s was released.', '%s - numeric version number' ),
423
- 'please-download-x' => _fs_text( 'Please download %s.' ),
424
- 'latest-x-version' => _fs_x( 'the latest %s version here',
425
- '%s - plan name, as the latest professional version here' ),
426
- 'trial-x-promotion-message' => _fs_text( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.' ),
427
- 'start-free-trial' => _fs_x( 'Start free trial', 'call to action' ),
428
- 'starting-trial' => _fs_text( 'Starting trial' ),
429
- 'please-wait' => _fs_text( 'Please wait' ),
430
- 'trial-cancel-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.' ),
431
- 'trial-utilized' => _fs_text( 'You already utilized a trial before.' ),
432
- 'in-trial-mode' => _fs_text( 'You are already running the %s in a trial mode.' ),
433
- 'trial-plan-x-not-exist' => _fs_text( 'Plan %s do not exist, therefore, can\'t start a trial.' ),
434
- 'plan-x-no-trial' => _fs_text( 'Plan %s does not support a trial period.' ),
435
- 'no-trials' => _fs_text( 'None of the %s\'s plans supports a trial period.' ),
436
- 'unexpected-api-error' => _fs_text( 'Unexpected API error. Please contact the %s\'s author with the following error.' ),
437
- 'no-commitment-for-x-days' => _fs_text( 'No commitment for %s days - cancel anytime!' ),
438
- 'license-expired-non-blocking-message' => _fs_text( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.' ),
439
- 'could-not-activate-x' => _fs_text( 'Couldn\'t activate %s.' ),
440
- 'contact-us-with-error-message' => _fs_text( 'Please contact us with the following message:' ),
441
- 'plan-did-not-change-message' => _fs_text( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.' ),
442
- 'contact-us-here' => _fs_text( 'Please contact us here' ),
443
- 'plan-did-not-change-email-message' => _fs_text( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.' ),
444
- #endregion Admin Notices
445
- #region Connectivity Issues
446
- 'connectivity-test-fails-message' => _fs_text( 'From unknown reason, the API connectivity test failed.' ),
447
- 'connectivity-test-maybe-temporary' => _fs_text( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?' ),
448
- 'curl-missing-message' => _fs_text( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.' ),
449
- 'curl-disabled-methods' => _fs_text( 'Disabled method(s):' ),
450
- 'cloudflare-blocks-connection-message' => _fs_text( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.' ),
451
- 'x-requires-access-to-api' => _fs_x( '%s requires an access to our API.',
452
- 'as pluginX requires an access to our API' ),
453
- 'squid-blocks-connection-message' => _fs_text( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.' ),
454
- 'squid-no-clue-title' => _fs_text( 'I don\'t know what is Squid or ACL, help me!' ),
455
- 'squid-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ),
456
- 'sysadmin-title' => _fs_text( 'I\'m a system administrator' ),
457
- 'squid-sysadmin-desc' => _fs_text( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.' ),
458
- 'curl-missing-no-clue-title' => _fs_text( 'I don\'t know what is cURL or how to install it, help me!' ),
459
- 'curl-missing-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ),
460
- 'curl-missing-sysadmin-desc' => _fs_text( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.' ),
461
- 'happy-to-resolve-issue-asap' => _fs_text( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.' ),
462
- 'contact-support-before-deactivation' => _fs_text( 'Sorry for the inconvenience and we are here to help if you give us a chance.' ),
463
- 'fix-issue-title' => _fs_text( 'Yes - I\'m giving you a chance to fix it' ),
464
- 'fix-issue-desc' => _fs_text( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.' ),
465
- 'install-previous-title' => _fs_text( 'Let\'s try your previous version' ),
466
- 'install-previous-desc' => _fs_text( 'Uninstall this version and install the previous one.' ),
467
- 'deactivate-plugin-title' => _fs_text( 'That\'s exhausting, please deactivate' ),
468
- 'deactivate-plugin-desc' => _fs_text( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.' ),
469
- 'fix-request-sent-message' => _fs_text( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.' ),
470
- 'server-blocking-access' => _fs_x( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s',
471
- '%1$s - plugin title, %2$s - API domain' ),
472
- 'wrong-authentication-param-message' => _fs_text( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.' ),
473
- #endregion Connectivity Issues
474
- #region Change Owner
475
- 'change-owner-request-sent-x' => _fs_text( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.' ),
476
- 'change-owner-request_owner-confirmed' => _fs_text( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.' ),
477
- 'change-owner-request_candidate-confirmed' => _fs_text( '%s is the new owner of the account.' ),
478
- #endregion Change Owner
479
- 'addon-x-cannot-run-without-y' => _fs_x( '%s cannot run without %s.',
480
- 'addonX cannot run without pluginY' ),
481
- 'addon-x-cannot-run-without-parent' => _fs_x( '%s cannot run without the plugin.', 'addonX cannot run...' ),
482
- 'plugin-x-activation-message' => _fs_x( '%s activation was successfully completed.',
483
- 'pluginX activation was successfully...' ),
484
- 'features-and-pricing' => _fs_x( 'Features & Pricing', 'Plugin installer section title' ),
485
- 'free-addon-not-deployed' => _fs_text( 'Add-on must be deployed to WordPress.org or Freemius.' ),
486
- 'paid-addon-not-deployed' => _fs_text( 'Paid add-on must be deployed to Freemius.' ),
487
- #--------------------------------------------------------------------------------
488
- #region Add-On Licensing
489
- #--------------------------------------------------------------------------------
490
- 'addon-no-license-message' => _fs_text( '%s is a premium only add-on. You have to purchase a license first before activating the plugin.' ),
491
- 'addon-trial-cancelled-message' => _fs_text( '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.' ),
492
- #endregion
493
- #--------------------------------------------------------------------------------
494
- #region Billing Cycles
495
- #--------------------------------------------------------------------------------
496
- 'monthly' => _fs_x( 'Monthly', 'as every month' ),
497
- 'mo' => _fs_x( 'mo', 'as monthly period' ),
498
- 'annual' => _fs_x( 'Annual', 'as once a year' ),
499
- 'annually' => _fs_x( 'Annually', 'as once a year' ),
500
- 'once' => _fs_x( 'Once', 'as once a year' ),
501
- 'year' => _fs_x( 'year', 'as annual period' ),
502
- 'lifetime' => _fs_text( 'Lifetime' ),
503
- 'best' => _fs_x( 'Best', 'e.g. the best product' ),
504
- 'billed-x' => _fs_x( 'Billed %s', 'e.g. billed monthly' ),
505
- 'save-x' => _fs_x( 'Save %s', 'as a discount of $5 or 10%' ),
506
- #endregion Billing Cycles
507
- 'view-details' => _fs_text( 'View details' ),
508
- #--------------------------------------------------------------------------------
509
- #region Trial
510
- #--------------------------------------------------------------------------------
511
- 'approve-start-trial' => _fs_x( 'Approve & Start Trial', 'button label' ),
512
- /* translators: %1$s: Number of trial days; %2$s: Plan name; */
513
- 'start-trial-prompt-header' => _fs_text( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.' ),
514
- /* translators: %s: Link to freemius.com */
515
- 'start-trial-prompt-message' => _fs_text( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.' ),
516
-
517
- #endregion
518
- #--------------------------------------------------------------------------------
519
- #region Billing Details
520
- #--------------------------------------------------------------------------------
521
- 'business-name' => _fs_text( 'Business name' ),
522
- 'tax-vat-id' => _fs_text( 'Tax / VAT ID' ),
523
- 'address-line-n' => _fs_text( 'Address Line %d' ),
524
- 'country' => _fs_text( 'Country' ),
525
- 'select-country' => _fs_text( 'Select Country' ),
526
- 'city' => _fs_text( 'City' ),
527
- 'town' => _fs_text( 'Town' ),
528
- 'state' => _fs_text( 'State' ),
529
- 'province' => _fs_text( 'Province' ),
530
- 'zip-postal-code' => _fs_text( 'ZIP / Postal Code' ),
531
- #endregion
532
- #--------------------------------------------------------------------------------
533
- #region Module Installation
534
- #--------------------------------------------------------------------------------
535
- 'installing-plugin-x' => _fs_text( 'Installing plugin: %s' ),
536
- 'auto-installation' => _fs_text( 'Automatic Installation' ),
537
- /* translators: %s: Number of seconds */
538
- 'x-sec' => _fs_text( '%s sec' ),
539
- 'installing-in-n' => _fs_text( 'An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.' ),
540
- 'installing-module-x' => _fs_text( 'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.' ),
541
- 'cancel-installation' => _fs_text( 'Cancel Installation' ),
542
- 'module-package-rename-failure' => _fs_text( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.' ),
543
- 'auto-install-error-invalid-id' => _fs_text( 'Invalid module ID.' ),
544
- 'auto-install-error-not-opted-in' => _fs_text( 'Auto installation only works for opted-in users.' ),
545
- 'auto-install-error-premium-activated' => _fs_text( 'Premium version already active.' ),
546
- 'auto-install-error-premium-addon-activated' => _fs_text( 'Premium add-on version already installed.' ),
547
- 'auto-install-error-invalid-license' => _fs_text( 'You do not have a valid license to access the premium version.' ),
548
- 'auto-install-error-serviceware' => _fs_text( 'Plugin is a "Serviceware" which means it does not have a premium code version.' ),
549
- #endregion
550
-
551
- /* translators: %s: Page name */
552
- 'secure-x-page-header' => _fs_text( 'Secure HTTPS %s page, running from an external domain' ),
553
- 'pci-compliant' => _fs_text( 'PCI compliant' ),
554
- 'view-paid-features' => _fs_text( 'View paid features' ),
555
- );
556
-
557
- /**
558
- * Localization of the strings in the plugin/theme info dialog box.
559
- *
560
- * $fs_module_info_text should ONLY include strings that are not located in $fs_text.
561
- *
562
- * @author Vova Feldman (@svovaf)
563
- * @since 1.2.2
564
- */
565
- global $fs_module_info_text;
566
-
567
- $fs_module_info_text = array(
568
- 'description' => _fs_x( 'Description', 'Plugin installer section title' ),
569
- 'installation' => _fs_x( 'Installation', 'Plugin installer section title' ),
570
- 'faq' => _fs_x( 'FAQ', 'Plugin installer section title' ),
571
- 'changelog' => _fs_x( 'Changelog', 'Plugin installer section title' ),
572
- 'reviews' => _fs_x( 'Reviews', 'Plugin installer section title' ),
573
- 'other_notes' => _fs_x( 'Other Notes', 'Plugin installer section title' ),
574
- /* translators: %s: 1 or One */
575
- 'x-star' => _fs_text( '%s star' ),
576
- /* translators: %s: Number larger than 1 */
577
- 'x-stars' => _fs_text( '%s stars' ),
578
- /* translators: %s: 1 or One */
579
- 'x-rating' => _fs_text( '%s rating' ),
580
- /* translators: %s: Number larger than 1 */
581
- 'x-ratings' => _fs_text( '%s ratings' ),
582
- /* translators: %s: 1 or One (Number of times downloaded) */
583
- 'x-time' => _fs_text( '%s time' ),
584
- /* translators: %s: Number of times downloaded */
585
- 'x-times' => _fs_text( '%s times' ),
586
- /* translators: %s: # of stars (e.g. 5 stars) */
587
- 'click-to-reviews' => _fs_text( 'Click to see reviews that provided a rating of %s' ),
588
- 'last-updated:' => _fs_text( 'Last Updated' ),
589
- 'requires-wordpress-version:' => _fs_text( 'Requires WordPress Version:' ),
590
- 'author:' => _fs_x( 'Author:', 'as the plugin author' ),
591
- 'compatible-up-to:' => _fs_text( 'Compatible up to:' ),
592
- 'downloaded:' => _fs_text( 'Downloaded:' ),
593
- 'wp-org-plugin-page' => _fs_text( 'WordPress.org Plugin Page' ),
594
- 'plugin-homepage' => _fs_text( 'Plugin Homepage' ),
595
- 'donate-to-plugin' => _fs_text( 'Donate to this plugin' ),
596
- 'average-rating' => _fs_text( 'Average Rating' ),
597
- 'based-on-x' => _fs_text( 'based on %s' ),
598
- 'warning:' => _fs_text( 'Warning:' ),
599
- 'contributors' => _fs_text( 'Contributors' ),
600
- 'plugin-install' => _fs_text( 'Plugin Install' ),
601
- 'not-tested-warning' => _fs_text( 'This plugin has not been tested with your current version of WordPress.' ),
602
- 'not-compatible-warning' => _fs_text( 'This plugin has not been marked as compatible with your version of WordPress.' ),
603
- 'newer-installed' => _fs_text( 'Newer Version (%s) Installed' ),
604
- 'latest-installed' => _fs_text( 'Latest Version Installed' ),
605
- );
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.1.4
7
+ *
8
+ * @deprecated This file is no longer in use. It's still in the project for backward compatibility.
9
+ */
10
+
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
+ }
14
+
15
+ require_once dirname( __FILE__ ) . '/l10n.php';
16
+
17
+ /**
18
+ * All strings can now be overridden.
19
+ *
20
+ * For example, if we want to override:
21
+ * 'you-are-step-away' => 'You are just one step away - %s',
22
+ *
23
+ * We can use the filter:
24
+ * fs_override_i18n( array(
25
+ * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ),
26
+ * 'skip' => __( 'Not today', '{your-text_domain}' ),
27
+ * ), '{plugin_slug}' );
28
+ *
29
+ * Or with the Freemius instance:
30
+ *
31
+ * my_freemius->override_i18n( array(
32
+ * 'opt-in-connect' => __( "Yes - I'm in!", '{your-text_domain}' ),
33
+ * 'skip' => __( 'Not today', '{your-text_domain}' ),
34
+ * ) );
35
+ */
36
+ global $fs_text;
37
+
38
+ $fs_text = array(
39
+ 'account' => _fs_text( 'Account' ),
40
+ 'addon' => _fs_text( 'Add-On' ),
41
+ 'contact-us' => _fs_text( 'Contact Us' ),
42
+ 'contact-support' => _fs_text( 'Contact Support' ),
43
+ 'change-ownership' => _fs_text( 'Change Ownership' ),
44
+ 'support' => _fs_text( 'Support' ),
45
+ 'support-forum' => _fs_text( 'Support Forum' ),
46
+ 'add-ons' => _fs_text( 'Add-Ons' ),
47
+ 'upgrade' => _fs_x( 'Upgrade', 'verb' ),
48
+ 'awesome' => _fs_text( 'Awesome' ),
49
+ 'pricing' => _fs_x( 'Pricing', 'noun' ),
50
+ 'price' => _fs_x( 'Price', 'noun' ),
51
+ 'unlimited-updates' => _fs_text( 'Unlimited Updates' ),
52
+ 'downgrade' => _fs_x( 'Downgrade', 'verb' ),
53
+ 'cancel-subscription' => _fs_x( 'Cancel Subscription', 'verb' ),
54
+ 'cancel-trial' => _fs_text( 'Cancel Trial' ),
55
+ 'free-trial' => _fs_text( 'Free Trial' ),
56
+ 'start-free-x' => _fs_text( 'Start my free %s' ),
57
+ 'no-commitment-x' => _fs_text( 'No commitment for %s - cancel anytime' ),
58
+ 'after-x-pay-as-little-y' => _fs_text( 'After your free %s, pay as little as %s' ),
59
+ 'details' => _fs_text( 'Details' ),
60
+ 'account-details' => _fs_text( 'Account Details' ),
61
+ 'delete' => _fs_x( 'Delete', 'verb' ),
62
+ 'show' => _fs_x( 'Show', 'verb' ),
63
+ 'hide' => _fs_x( 'Hide', 'verb' ),
64
+ 'edit' => _fs_x( 'Edit', 'verb' ),
65
+ 'update' => _fs_x( 'Update', 'verb' ),
66
+ 'date' => _fs_text( 'Date' ),
67
+ 'amount' => _fs_text( 'Amount' ),
68
+ 'invoice' => _fs_text( 'Invoice' ),
69
+ 'billing' => _fs_text( 'Billing' ),
70
+ 'payments' => _fs_text( 'Payments' ),
71
+ 'delete-account' => _fs_text( 'Delete Account' ),
72
+ 'dismiss' => _fs_x( 'Dismiss', 'as close a window' ),
73
+ 'plan' => _fs_x( 'Plan', 'as product pricing plan' ),
74
+ 'change-plan' => _fs_text( 'Change Plan' ),
75
+ 'download-x-version' => _fs_x( 'Download %s Version', 'as download professional version' ),
76
+ 'download-x-version-now' => _fs_x( 'Download %s version now', 'as download professional version now' ),
77
+ 'download-latest' => _fs_x( 'Download Latest', 'as download latest version' ),
78
+ 'you-have-x-license' => _fs_x( 'You have a %s license.', 'E.g. you have a professional license.' ),
79
+ 'new' => _fs_text( 'New' ),
80
+ 'free' => _fs_text( 'Free' ),
81
+ 'trial' => _fs_x( 'Trial', 'as trial plan' ),
82
+ 'start-trial' => _fs_x( 'Start Trial', 'as starting a trial plan' ),
83
+ 'purchase' => _fs_x( 'Purchase', 'verb' ),
84
+ 'purchase-license' => _fs_text( 'Purchase License' ),
85
+ 'buy' => _fs_x( 'Buy', 'verb' ),
86
+ 'buy-license' => _fs_text( 'Buy License' ),
87
+ 'license-single-site' => _fs_text( 'Single Site License' ),
88
+ 'license-unlimited' => _fs_text( 'Unlimited Licenses' ),
89
+ 'license-x-sites' => _fs_text( 'Up to %s Sites' ),
90
+ 'renew-license-now' => _fs_text( '%sRenew your license now%s to access version %s security & feature updates, and support.' ),
91
+ 'ask-for-upgrade-email-address' => _fs_text( "Enter the email address you've used for the upgrade below and we will resend you the license key." ),
92
+ 'x-plan' => _fs_x( '%s Plan', 'e.g. Professional Plan' ),
93
+ 'you-are-step-away' => _fs_text( 'You are just one step away - %s' ),
94
+ 'activate-x-now' => _fs_x( 'Complete "%s" Activation Now',
95
+ '%s - plugin name. As complete "Jetpack" activation now' ),
96
+ 'few-plugin-tweaks' => _fs_text( 'We made a few tweaks to the %s, %s' ),
97
+ 'optin-x-now' => _fs_text( 'Opt in to make "%s" better!' ),
98
+ 'error' => _fs_text( 'Error' ),
99
+ 'failed-finding-main-path' => _fs_text( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.' ),
100
+ 'learn-more' => _fs_text( 'Learn more' ),
101
+ 'license_not_whitelabeled' => _fs_text( "Is this your client's site? %s if you wish to hide sensitive info like your billing address and invoices from the WP Admin."),
102
+ 'license_whitelabeled' => _fs_text( 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your billing address and invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.'),
103
+
104
+ #region Affiliation
105
+ 'affiliation' => _fs_text( 'Affiliation' ),
106
+ 'affiliate' => _fs_text( 'Affiliate' ),
107
+ 'affiliate-tracking' => _fs_text( '%s tracking cookie after the first visit to maximize earnings potential.' ),
108
+ 'renewals-commission' => _fs_text( 'Get commission for automated subscription renewals.' ),
109
+ 'affiliate-application-accepted' => _fs_text( "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." ),
110
+ 'affiliate-application-thank-you' => _fs_text( "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." ),
111
+ 'affiliate-application-rejected' => _fs_text( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." ),
112
+ 'affiliate-account-suspended' => _fs_text( 'Your affiliation account was temporarily suspended.' ),
113
+ 'affiliate-account-blocked' => _fs_text( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.' ),
114
+ 'become-an-ambassador' => _fs_text( 'Like the %s? Become our ambassador and earn cash ;-)' ),
115
+ 'become-an-ambassador-admin-notice' => _fs_text( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!' ),
116
+ 'refer-new-customers' => _fs_text( 'Refer new customers to our %s and earn %s commission on each successful sale you refer!' ),
117
+ 'program-summary' => _fs_text( 'Program Summary' ),
118
+ 'commission-on-new-license-purchase' => _fs_text( '%s commission when a customer purchases a new license.' ),
119
+ 'unlimited-commissions' => _fs_text( 'Unlimited commissions.' ),
120
+ 'minimum-payout-amount' => _fs_text( '%s minimum payout amount.' ),
121
+ 'payouts-unit-and-processing' => _fs_text( 'Payouts are in USD and processed monthly via PayPal.' ),
122
+ 'commission-payment' => _fs_text( 'As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.' ),
123
+ 'become-an-affiliate' => _fs_text( 'Become an affiliate' ),
124
+ 'apply-to-become-an-affiliate' => _fs_text( 'Apply to become an affiliate' ),
125
+ 'full-name' => _fs_text( 'Full name' ),
126
+ 'paypal-account-email-address' => _fs_text( 'PayPal account email address' ),
127
+ 'promotion-methods' => _fs_text( 'Promotion methods' ),
128
+ 'social-media' => _fs_text( 'Social media (Facebook, Twitter, etc.)' ),
129
+ 'mobile-apps' => _fs_text( 'Mobile apps' ),
130
+ 'statistics-information-field-label' => _fs_text( 'Website, email, and social media statistics (optional)' ),
131
+ 'statistics-information-field-desc' => _fs_text( 'Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).' ),
132
+ 'promotion-method-desc-field-label' => _fs_text( 'How will you promote us?' ),
133
+ 'promotion-method-desc-field-desc' => _fs_text( 'Please provide details on how you intend to promote %s (please be as specific as possible).' ),
134
+ 'domain-field-label' => _fs_text( 'Where are you going to promote the %s?' ),
135
+ 'domain-field-desc' => _fs_text( 'Enter the domain of your website or other websites from where you plan to promote the %s.' ),
136
+ 'extra-domain-fields-label' => _fs_text( 'Extra Domains' ),
137
+ 'extra-domain-fields-desc' => _fs_text( 'Extra domains where you will be marketing the product from.' ),
138
+ 'add-another-domain' => _fs_text( 'Add another domain' ),
139
+ 'remove' => _fs_x( 'Remove', 'Remove domain' ),
140
+ 'email-address-is-required' => _fs_text( 'Email address is required.' ),
141
+ 'domain-is-required' => _fs_text( 'Domain is required.' ),
142
+ 'invalid-domain' => _fs_text( 'Invalid domain' ),
143
+ 'paypal-email-address-is-required' => _fs_text( 'PayPal email address is required.' ),
144
+ 'processing' => _fs_text( 'Processing...' ),
145
+ 'non-expiring' => _fs_text( 'Non-expiring' ),
146
+ 'account-is-pending-activation' => _fs_text( 'Account is pending activation.' ),
147
+ #endregion Affiliation
148
+
149
+ #region Account
150
+ 'expiration' => _fs_x( 'Expiration', 'as expiration date' ),
151
+ 'license' => _fs_x( 'License', 'as software license' ),
152
+ 'not-verified' => _fs_text( 'not verified' ),
153
+ 'verify-email' => _fs_text( 'Verify Email' ),
154
+ 'expires-in' => _fs_x( 'Expires in %s', 'e.g. expires in 2 months' ),
155
+ 'renews-in' => _fs_x( 'Auto renews in %s', 'e.g. auto renews in 2 months' ),
156
+ 'no-expiration' => _fs_text( 'No expiration' ),
157
+ 'expired' => _fs_text( 'Expired' ),
158
+ 'cancelled' => _fs_text( 'Cancelled' ),
159
+ 'in-x' => _fs_x( 'In %s', 'e.g. In 2 hours' ),
160
+ 'x-ago' => _fs_x( '%s ago', 'e.g. 2 min ago' ),
161
+ /* translators: %s: Version number (e.g. 4.6 or higher) */
162
+ 'x-or-higher' => _fs_text( '%s or higher' ),
163
+ 'version' => _fs_x( 'Version', 'as plugin version' ),
164
+ 'name' => _fs_text( 'Name' ),
165
+ 'email' => _fs_text( 'Email' ),
166
+ 'email-address' => _fs_text( 'Email address' ),
167
+ 'verified' => _fs_text( 'Verified' ),
168
+ 'module' => _fs_text( 'Module' ),
169
+ 'module-type' => _fs_text( 'Module Type' ),
170
+ 'plugin' => _fs_text( 'Plugin' ),
171
+ 'plugins' => _fs_text( 'Plugins' ),
172
+ 'theme' => _fs_text( 'Theme' ),
173
+ 'themes' => _fs_text( 'Themes' ),
174
+ 'path' => _fs_x( 'Path', 'as file/folder path' ),
175
+ 'title' => _fs_text( 'Title' ),
176
+ 'free-version' => _fs_text( 'Free version' ),
177
+ 'premium-version' => _fs_text( 'Premium version' ),
178
+ 'slug' => _fs_x( 'Slug', 'as WP plugin slug' ),
179
+ 'id' => _fs_text( 'ID' ),
180
+ 'users' => _fs_text( 'Users' ),
181
+ 'module-installs' => _fs_text( '%s Installs' ),
182
+ 'sites' => _fs_x( 'Sites', 'like websites' ),
183
+ 'user-id' => _fs_text( 'User ID' ),
184
+ 'site-id' => _fs_text( 'Site ID' ),
185
+ 'public-key' => _fs_text( 'Public Key' ),
186
+ 'secret-key' => _fs_text( 'Secret Key' ),
187
+ 'no-secret' => _fs_x( 'No Secret', 'as secret encryption key missing' ),
188
+ 'no-id' => _fs_text( 'No ID' ),
189
+ 'sync-license' => _fs_x( 'Sync License', 'as synchronize license' ),
190
+ 'sync' => _fs_x( 'Sync', 'as synchronize' ),
191
+ 'activate-license' => _fs_text( 'Activate License' ),
192
+ 'activate-free-version' => _fs_text( 'Activate Free Version' ),
193
+ 'activate-license-message' => _fs_text( 'Please enter the license key that you received in the email right after the purchase:' ),
194
+ 'activating-license' => _fs_text( 'Activating license...' ),
195
+ 'change-license' => _fs_text( 'Change License' ),
196
+ 'update-license' => _fs_text( 'Update License' ),
197
+ 'deactivate-license' => _fs_text( 'Deactivate License' ),
198
+ 'activate' => _fs_text( 'Activate' ),
199
+ 'deactivate' => _fs_text( 'Deactivate' ),
200
+ 'skip-deactivate' => _fs_text( 'Skip & Deactivate' ),
201
+ 'skip-and-x' => _fs_text( 'Skip & %s' ),
202
+ 'no-deactivate' => _fs_text( 'No - just deactivate' ),
203
+ 'yes-do-your-thing' => _fs_text( 'Yes - do your thing' ),
204
+ 'active' => _fs_x( 'Active', 'active mode' ),
205
+ 'is-active' => _fs_x( 'Is Active', 'is active mode?' ),
206
+ 'install-now' => _fs_text( 'Install Now' ),
207
+ 'install-update-now' => _fs_text( 'Install Update Now' ),
208
+ 'more-information-about-x' => _fs_text( 'More information about %s' ),
209
+ 'localhost' => _fs_text( 'Localhost' ),
210
+ 'activate-x-plan' => _fs_x( 'Activate %s Plan', 'as activate Professional plan' ),
211
+ 'x-left' => _fs_x( '%s left', 'as 5 licenses left' ),
212
+ 'last-license' => _fs_text( 'Last license' ),
213
+ 'what-is-your-x' => _fs_text( 'What is your %s?' ),
214
+ 'activate-this-addon' => _fs_text( 'Activate this add-on' ),
215
+ 'deactivate-license-confirm' => _fs_text( 'Deactivating your license will block all premium features, but will enable you to activate the license on another site. Are you sure you want to proceed?' ),
216
+ 'delete-account-x-confirm' => _fs_text( 'Deleting the account will automatically deactivate your %s plan license so you can use it on other sites. If you want to terminate the recurring payments as well, click the "Cancel" button, and first "Downgrade" your account. Are you sure you would like to continue with the deletion?' ),
217
+ 'delete-account-confirm' => _fs_text( 'Deletion is not temporary. Only delete if you no longer want to use this %s anymore. Are you sure you would like to continue with the deletion?' ),
218
+ 'downgrade-x-confirm' => _fs_text( 'Downgrading your plan will immediately stop all future recurring payments and your %s plan license will expire in %s.' ),
219
+ 'cancel-trial-confirm' => _fs_text( 'Cancelling the trial will immediately block access to all premium features. Are you sure?' ),
220
+ 'after-downgrade-non-blocking' => _fs_text( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.' ),
221
+ 'after-downgrade-blocking' => _fs_text( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.' ),
222
+ 'proceed-confirmation' => _fs_text( 'Are you sure you want to proceed?' ),
223
+ #endregion Account
224
+
225
+ 'add-ons-for-x' => _fs_text( 'Add Ons for %s' ),
226
+ 'add-ons-missing' => _fs_text( 'We could\'nt load the add-ons list. It\'s probably an issue on our side, please try to come back in few minutes.' ),
227
+ #region Plugin Deactivation
228
+ 'anonymous-feedback' => _fs_text( 'Anonymous feedback' ),
229
+ 'quick-feedback' => _fs_text( 'Quick feedback' ),
230
+ 'deactivation-share-reason' => _fs_text( 'If you have a moment, please let us know why you are %s' ),
231
+ 'deactivating' => _fs_text( 'deactivating' ),
232
+ 'deactivation' => _fs_text( 'Deactivation' ),
233
+ 'theme-switch' => _fs_text( 'Theme Switch' ),
234
+ 'switching' => _fs_text( 'switching' ),
235
+ 'switch' => _fs_text( 'Switch' ),
236
+ 'activate-x' => _fs_text( 'Activate %s' ),
237
+ 'deactivation-modal-button-confirm' => _fs_text( 'Yes - %s' ),
238
+ 'deactivation-modal-button-submit' => _fs_text( 'Submit & %s' ),
239
+ 'cancel' => _fs_text( 'Cancel' ),
240
+ 'reason-no-longer-needed' => _fs_text( 'I no longer need the %s' ),
241
+ 'reason-found-a-better-plugin' => _fs_text( 'I found a better %s' ),
242
+ 'reason-needed-for-a-short-period' => _fs_text( 'I only needed the %s for a short period' ),
243
+ 'reason-broke-my-site' => _fs_text( 'The %s broke my site' ),
244
+ 'reason-suddenly-stopped-working' => _fs_text( 'The %s suddenly stopped working' ),
245
+ 'reason-cant-pay-anymore' => _fs_text( "I can't pay for it anymore" ),
246
+ 'reason-temporary-deactivation' => _fs_text( "It's a temporary deactivation. I'm just debugging an issue." ),
247
+ 'reason-temporary-x' => _fs_text( "It's a temporary %s. I'm just debugging an issue." ),
248
+ 'reason-other' => _fs_x( 'Other',
249
+ 'the text of the "other" reason for deactivating the module that is shown in the modal box.' ),
250
+ 'ask-for-reason-message' => _fs_text( 'Kindly tell us the reason so we can improve.' ),
251
+ 'placeholder-plugin-name' => _fs_text( "What's the %s's name?" ),
252
+ 'placeholder-comfortable-price' => _fs_text( 'What price would you feel comfortable paying?' ),
253
+ 'reason-couldnt-make-it-work' => _fs_text( "I couldn't understand how to make it work" ),
254
+ 'reason-great-but-need-specific-feature' => _fs_text( "The %s is great, but I need specific feature that you don't support" ),
255
+ 'reason-not-working' => _fs_text( 'The %s is not working' ),
256
+ 'reason-not-what-i-was-looking-for' => _fs_text( "It's not what I was looking for" ),
257
+ 'reason-didnt-work-as-expected' => _fs_text( "The %s didn't work as expected" ),
258
+ 'placeholder-feature' => _fs_text( 'What feature?' ),
259
+ 'placeholder-share-what-didnt-work' => _fs_text( "Kindly share what didn't work so we can fix it for future users..." ),
260
+ 'placeholder-what-youve-been-looking-for' => _fs_text( "What you've been looking for?" ),
261
+ 'placeholder-what-did-you-expect' => _fs_text( "What did you expect?" ),
262
+ 'reason-didnt-work' => _fs_text( "The %s didn't work" ),
263
+ 'reason-dont-like-to-share-my-information' => _fs_text( "I don't like to share my information with you" ),
264
+ 'dont-have-to-share-any-data' => _fs_text( "You might have missed it, but you don't have to share any data and can just %s the opt-in." ),
265
+ #endregion Plugin Deactivation
266
+
267
+ #region Connect
268
+ 'hey-x' => _fs_x( 'Hey %s,', 'greeting' ),
269
+ 'thanks-x' => _fs_x( 'Thanks %s!', 'a greeting. E.g. Thanks John!' ),
270
+ 'connect-message' => _fs_text( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.' ),
271
+ 'connect-message_on-update' => _fs_text( 'Please help us improve %1$s! If you opt in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.' ),
272
+ 'pending-activation-message' => _fs_text( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.' ),
273
+ 'complete-the-install' => _fs_text( 'complete the install' ),
274
+ 'start-the-trial' => _fs_text( 'start the trial' ),
275
+ 'thanks-for-purchasing' => _fs_text( 'Thanks for purchasing %s! To get started, please enter your license key:' ),
276
+ 'license-sync-disclaimer' => _fs_text( 'The %1$s will be periodically sending data to %2$s to check for security and feature updates, and verify the validity of your license.' ),
277
+ 'what-permissions' => _fs_text( 'What permissions are being granted?' ),
278
+ 'permissions-profile' => _fs_text( 'Your Profile Overview' ),
279
+ 'permissions-profile_desc' => _fs_text( 'Name and email address' ),
280
+ 'permissions-site' => _fs_text( 'Your Site Overview' ),
281
+ 'permissions-site_desc' => _fs_text( 'Site URL, WP version, PHP info, plugins & themes' ),
282
+ 'permissions-events' => _fs_text( 'Current %s Events' ),
283
+ 'permissions-events_desc' => _fs_text( 'Activation, deactivation and uninstall' ),
284
+ 'permissions-plugins_themes' => _fs_text( 'Plugins & Themes' ),
285
+ 'permissions-plugins_themes_desc' => _fs_text( 'Titles, versions and state.' ),
286
+ 'permissions-admin-notices' => _fs_text( 'Admin Notices' ),
287
+ 'permissions-newsletter' => _fs_text( 'Newsletter' ),
288
+ 'permissions-newsletter_desc' => _fs_text( 'Updates, announcements, marketing, no spam' ),
289
+ 'privacy-policy' => _fs_text( 'Privacy Policy' ),
290
+ 'tos' => _fs_text( 'Terms of Service' ),
291
+ 'activating' => _fs_x( 'Activating', 'as activating plugin' ),
292
+ 'sending-email' => _fs_x( 'Sending email', 'as in the process of sending an email' ),
293
+ 'opt-in-connect' => _fs_x( 'Allow & Continue', 'button label' ),
294
+ 'agree-activate-license' => _fs_x( 'Agree & Activate License', 'button label' ),
295
+ 'skip' => _fs_x( 'Skip', 'verb' ),
296
+ 'click-here-to-use-plugin-anonymously' => _fs_text( 'Click here to use the plugin anonymously' ),
297
+ 'resend-activation-email' => _fs_text( 'Re-send activation email' ),
298
+ 'license-key' => _fs_text( 'License key' ),
299
+ 'send-license-key' => _fs_text( 'Send License Key' ),
300
+ 'sending-license-key' => _fs_text( 'Sending license key' ),
301
+ 'have-license-key' => _fs_text( 'Have a license key?' ),
302
+ 'dont-have-license-key' => _fs_text( 'Don\'t have a license key?' ),
303
+ 'cant-find-license-key' => _fs_text( "Can't find your license key?" ),
304
+ 'email-not-found' => _fs_text( "We couldn't find your email address in the system, are you sure it's the right address?" ),
305
+ 'no-active-licenses' => _fs_text( "We can't see any active licenses associated with that email address, are you sure it's the right address?" ),
306
+ 'opt-in' => _fs_text( 'Opt In' ),
307
+ 'opt-out' => _fs_text( 'Opt Out' ),
308
+ 'opt-out-cancel' => _fs_text( 'On second thought - I want to continue helping' ),
309
+ 'opting-out' => _fs_text( 'Opting out...' ),
310
+ 'opting-in' => _fs_text( 'Opting in...' ),
311
+ 'opt-out-message-appreciation' => _fs_text( 'We appreciate your help in making the %s better by letting us track some usage data.' ),
312
+ 'opt-out-message-usage-tracking' => _fs_text( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking." ),
313
+ 'opt-out-message-clicking-opt-out' => _fs_text( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.' ),
314
+ 'apply-on-all-sites-in-the-network' => _fs_text( 'Apply on all sites in the network.' ),
315
+ 'delegate-to-site-admins' => _fs_text( 'Delegate to Site Admins' ),
316
+ 'delegate-to-site-admins-and-continue' => _fs_text( 'Delegate to Site Admins & Continue' ),
317
+ 'continue' => _fs_text( 'Continue' ),
318
+ 'allow' => _fs_text( 'allow' ),
319
+ 'delegate' => _fs_text( 'delegate' ),
320
+ #endregion Connect
321
+
322
+ #region Screenshots
323
+ 'screenshots' => _fs_text( 'Screenshots' ),
324
+ 'view-full-size-x' => _fs_text( 'Click to view full-size screenshot %d' ),
325
+ #endregion Screenshots
326
+
327
+ #region Debug
328
+ 'freemius-debug' => _fs_text( 'Freemius Debug' ),
329
+ 'on' => _fs_x( 'On', 'as turned on' ),
330
+ 'off' => _fs_x( 'Off', 'as turned off' ),
331
+ 'debugging' => _fs_x( 'Debugging', 'as code debugging' ),
332
+ 'freemius-state' => _fs_text( 'Freemius State' ),
333
+ 'connected' => _fs_x( 'Connected', 'as connection was successful' ),
334
+ 'blocked' => _fs_x( 'Blocked', 'as connection blocked' ),
335
+ 'api' => _fs_x( 'API', 'as application program interface' ),
336
+ 'sdk' => _fs_x( 'SDK', 'as software development kit versions' ),
337
+ 'sdk-versions' => _fs_x( 'SDK Versions', 'as software development kit versions' ),
338
+ 'plugin-path' => _fs_x( 'Plugin Path', 'as plugin folder path' ),
339
+ 'sdk-path' => _fs_x( 'SDK Path', 'as sdk path' ),
340
+ 'addons-of-x' => _fs_text( 'Add Ons of Plugin %s' ),
341
+ 'delete-all-confirm' => _fs_text( 'Are you sure you want to delete all Freemius data?' ),
342
+ 'actions' => _fs_text( 'Actions' ),
343
+ 'delete-all-accounts' => _fs_text( 'Delete All Accounts' ),
344
+ 'start-fresh' => _fs_text( 'Start Fresh' ),
345
+ 'clear-api-cache' => _fs_text( 'Clear API Cache' ),
346
+ 'sync-data-from-server' => _fs_text( 'Sync Data From Server' ),
347
+ 'scheduled-crons' => _fs_text( 'Scheduled Crons' ),
348
+ 'cron-type' => _fs_text( 'Cron Type' ),
349
+ 'plugins-themes-sync' => _fs_text( 'Plugins & Themes Sync' ),
350
+ 'module-licenses' => _fs_text( '%s Licenses' ),
351
+ 'debug-log' => _fs_text( 'Debug Log' ),
352
+ 'all' => _fs_text( 'All' ),
353
+ 'file' => _fs_text( 'File' ),
354
+ 'function' => _fs_text( 'Function' ),
355
+ 'process-id' => _fs_text( 'Process ID' ),
356
+ 'logger' => _fs_text( 'Logger' ),
357
+ 'message' => _fs_text( 'Message' ),
358
+ 'download' => _fs_text( 'Download' ),
359
+ 'filter' => _fs_text( 'Filter' ),
360
+ 'type' => _fs_text( 'Type' ),
361
+ 'all-types' => _fs_text( 'All Types' ),
362
+ 'all-requests' => _fs_text( 'All Requests' ),
363
+ #endregion Debug
364
+
365
+ #region Expressions
366
+ 'congrats' => _fs_x( 'Congrats', 'as congratulations' ),
367
+ 'oops' => _fs_x( 'Oops', 'exclamation' ),
368
+ 'yee-haw' => _fs_x( 'Yee-haw', 'interjection expressing joy or exuberance' ),
369
+ 'woot' => _fs_x( 'W00t',
370
+ '(especially in electronic communication) used to express elation, enthusiasm, or triumph.' ),
371
+ 'right-on' => _fs_x( 'Right on', 'a positive response' ),
372
+ 'hmm' => _fs_x( 'Hmm',
373
+ 'something somebody says when they are thinking about what you have just said. ' ),
374
+ 'ok' => _fs_text( 'O.K' ),
375
+ 'hey' => _fs_x( 'Hey', 'exclamation' ),
376
+ 'heads-up' => _fs_x( 'Heads up',
377
+ 'advance notice of something that will need attention.' ),
378
+ #endregion Expressions
379
+
380
+ #region Admin Notices
381
+ 'you-have-latest' => _fs_text( 'Seems like you got the latest release.' ),
382
+ 'you-are-good' => _fs_text( 'You are all good!' ),
383
+ 'user-exist-message' => _fs_text( 'Sorry, we could not complete the email update. Another user with the same email is already registered.' ),
384
+ 'user-exist-message_ownership' => _fs_text( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.' ),
385
+ 'email-updated-message' => _fs_text( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.' ),
386
+ 'name-updated-message' => _fs_text( 'Your name was successfully updated.' ),
387
+ 'x-updated' => _fs_text( 'You have successfully updated your %s.' ),
388
+ 'name-update-failed-message' => _fs_text( 'Please provide your full name.' ),
389
+ 'verification-email-sent-message' => _fs_text( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.' ),
390
+ 'addons-info-external-message' => _fs_text( 'Just letting you know that the add-ons information of %s is being pulled from an external server.' ),
391
+ 'no-cc-required' => _fs_text( 'No credit card required' ),
392
+ 'premium-activated-message' => _fs_text( 'Premium %s version was successfully activated.' ),
393
+ 'successful-version-upgrade-message' => _fs_text( 'The upgrade of %s was successfully completed.' ),
394
+ 'activation-with-plan-x-message' => _fs_text( 'Your account was successfully activated with the %s plan.' ),
395
+ 'download-latest-x-version-now' => _fs_text( 'Download the latest %s version now' ),
396
+ 'follow-steps-to-complete-upgrade' => _fs_text( 'Please follow these steps to complete the upgrade' ),
397
+ 'download-latest-x-version' => _fs_text( 'Download the latest %s version' ),
398
+ 'download-latest-version' => _fs_text( 'Download the latest version' ),
399
+ 'deactivate-free-version' => _fs_text( 'Deactivate the free version' ),
400
+ 'upload-and-activate' => _fs_text( 'Upload and activate the downloaded version' ),
401
+ 'howto-upload-activate' => _fs_text( 'How to upload and activate?' ),
402
+ 'addon-successfully-purchased-message' => _fs_x( '%s Add-on was successfully purchased.',
403
+ '%s - product name, e.g. Facebook add-on was successfully...' ),
404
+ 'addon-successfully-upgraded-message' => _fs_text( 'Your %s Add-on plan was successfully upgraded.' ),
405
+ 'email-verified-message' => _fs_text( 'Your email has been successfully verified - you are AWESOME!' ),
406
+ 'plan-upgraded-message' => _fs_text( 'Your plan was successfully upgraded.' ),
407
+ 'plan-changed-to-x-message' => _fs_text( 'Your plan was successfully changed to %s.' ),
408
+ 'license-expired-blocking-message' => _fs_text( 'Your license has expired. You can still continue using the free %s forever.' ),
409
+ 'license-cancelled' => _fs_text( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.' ),
410
+ 'trial-started-message' => _fs_text( 'Your trial has been successfully started.' ),
411
+ 'license-activated-message' => _fs_text( 'Your license was successfully activated.' ),
412
+ 'no-active-license-message' => _fs_text( 'It looks like your site currently doesn\'t have an active license.' ),
413
+ 'license-deactivation-message' => _fs_text( 'Your license was successfully deactivated, you are back to the %s plan.' ),
414
+ 'license-deactivation-failed-message' => _fs_text( 'It looks like the license deactivation failed.' ),
415
+ 'license-activation-failed-message' => _fs_text( 'It looks like the license could not be activated.' ),
416
+ 'server-error-message' => _fs_text( 'Error received from the server:' ),
417
+ 'trial-expired-message' => _fs_text( 'Your trial has expired. You can still continue using all our free features.' ),
418
+ 'plan-x-downgraded-message' => _fs_text( 'Your plan was successfully downgraded. Your %s plan license will expire in %s.' ),
419
+ 'plan-downgraded-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your plan downgrade. Please try again in few minutes.' ),
420
+ 'trial-cancel-no-trial-message' => _fs_text( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)' ),
421
+ 'trial-cancel-message' => _fs_text( 'Your %s free trial was successfully cancelled.' ),
422
+ 'version-x-released' => _fs_x( 'Version %s was released.', '%s - numeric version number' ),
423
+ 'please-download-x' => _fs_text( 'Please download %s.' ),
424
+ 'latest-x-version' => _fs_x( 'the latest %s version here',
425
+ '%s - plan name, as the latest professional version here' ),
426
+ 'trial-x-promotion-message' => _fs_text( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.' ),
427
+ 'start-free-trial' => _fs_x( 'Start free trial', 'call to action' ),
428
+ 'starting-trial' => _fs_text( 'Starting trial' ),
429
+ 'please-wait' => _fs_text( 'Please wait' ),
430
+ 'trial-cancel-failure-message' => _fs_text( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.' ),
431
+ 'trial-utilized' => _fs_text( 'You already utilized a trial before.' ),
432
+ 'in-trial-mode' => _fs_text( 'You are already running the %s in a trial mode.' ),
433
+ 'trial-plan-x-not-exist' => _fs_text( 'Plan %s do not exist, therefore, can\'t start a trial.' ),
434
+ 'plan-x-no-trial' => _fs_text( 'Plan %s does not support a trial period.' ),
435
+ 'no-trials' => _fs_text( 'None of the %s\'s plans supports a trial period.' ),
436
+ 'unexpected-api-error' => _fs_text( 'Unexpected API error. Please contact the %s\'s author with the following error.' ),
437
+ 'no-commitment-for-x-days' => _fs_text( 'No commitment for %s days - cancel anytime!' ),
438
+ 'license-expired-non-blocking-message' => _fs_text( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.' ),
439
+ 'could-not-activate-x' => _fs_text( 'Couldn\'t activate %s.' ),
440
+ 'contact-us-with-error-message' => _fs_text( 'Please contact us with the following message:' ),
441
+ 'plan-did-not-change-message' => _fs_text( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.' ),
442
+ 'contact-us-here' => _fs_text( 'Please contact us here' ),
443
+ 'plan-did-not-change-email-message' => _fs_text( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.' ),
444
+ #endregion Admin Notices
445
+ #region Connectivity Issues
446
+ 'connectivity-test-fails-message' => _fs_text( 'From unknown reason, the API connectivity test failed.' ),
447
+ 'connectivity-test-maybe-temporary' => _fs_text( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?' ),
448
+ 'curl-missing-message' => _fs_text( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.' ),
449
+ 'curl-disabled-methods' => _fs_text( 'Disabled method(s):' ),
450
+ 'cloudflare-blocks-connection-message' => _fs_text( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.' ),
451
+ 'x-requires-access-to-api' => _fs_x( '%s requires an access to our API.',
452
+ 'as pluginX requires an access to our API' ),
453
+ 'squid-blocks-connection-message' => _fs_text( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.' ),
454
+ 'squid-no-clue-title' => _fs_text( 'I don\'t know what is Squid or ACL, help me!' ),
455
+ 'squid-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ),
456
+ 'sysadmin-title' => _fs_text( 'I\'m a system administrator' ),
457
+ 'squid-sysadmin-desc' => _fs_text( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.' ),
458
+ 'curl-missing-no-clue-title' => _fs_text( 'I don\'t know what is cURL or how to install it, help me!' ),
459
+ 'curl-missing-no-clue-desc' => _fs_text( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.' ),
460
+ 'curl-missing-sysadmin-desc' => _fs_text( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.' ),
461
+ 'happy-to-resolve-issue-asap' => _fs_text( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.' ),
462
+ 'contact-support-before-deactivation' => _fs_text( 'Sorry for the inconvenience and we are here to help if you give us a chance.' ),
463
+ 'fix-issue-title' => _fs_text( 'Yes - I\'m giving you a chance to fix it' ),
464
+ 'fix-issue-desc' => _fs_text( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.' ),
465
+ 'install-previous-title' => _fs_text( 'Let\'s try your previous version' ),
466
+ 'install-previous-desc' => _fs_text( 'Uninstall this version and install the previous one.' ),
467
+ 'deactivate-plugin-title' => _fs_text( 'That\'s exhausting, please deactivate' ),
468
+ 'deactivate-plugin-desc' => _fs_text( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.' ),
469
+ 'fix-request-sent-message' => _fs_text( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.' ),
470
+ 'server-blocking-access' => _fs_x( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s',
471
+ '%1$s - plugin title, %2$s - API domain' ),
472
+ 'wrong-authentication-param-message' => _fs_text( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.' ),
473
+ #endregion Connectivity Issues
474
+ #region Change Owner
475
+ 'change-owner-request-sent-x' => _fs_text( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.' ),
476
+ 'change-owner-request_owner-confirmed' => _fs_text( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.' ),
477
+ 'change-owner-request_candidate-confirmed' => _fs_text( '%s is the new owner of the account.' ),
478
+ #endregion Change Owner
479
+ 'addon-x-cannot-run-without-y' => _fs_x( '%s cannot run without %s.',
480
+ 'addonX cannot run without pluginY' ),
481
+ 'addon-x-cannot-run-without-parent' => _fs_x( '%s cannot run without the plugin.', 'addonX cannot run...' ),
482
+ 'plugin-x-activation-message' => _fs_x( '%s activation was successfully completed.',
483
+ 'pluginX activation was successfully...' ),
484
+ 'features-and-pricing' => _fs_x( 'Features & Pricing', 'Plugin installer section title' ),
485
+ 'free-addon-not-deployed' => _fs_text( 'Add-on must be deployed to WordPress.org or Freemius.' ),
486
+ 'paid-addon-not-deployed' => _fs_text( 'Paid add-on must be deployed to Freemius.' ),
487
+ #--------------------------------------------------------------------------------
488
+ #region Add-On Licensing
489
+ #--------------------------------------------------------------------------------
490
+ 'addon-no-license-message' => _fs_text( '%s is a premium only add-on. You have to purchase a license first before activating the plugin.' ),
491
+ 'addon-trial-cancelled-message' => _fs_text( '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.' ),
492
+ #endregion
493
+ #--------------------------------------------------------------------------------
494
+ #region Billing Cycles
495
+ #--------------------------------------------------------------------------------
496
+ 'monthly' => _fs_x( 'Monthly', 'as every month' ),
497
+ 'mo' => _fs_x( 'mo', 'as monthly period' ),
498
+ 'annual' => _fs_x( 'Annual', 'as once a year' ),
499
+ 'annually' => _fs_x( 'Annually', 'as once a year' ),
500
+ 'once' => _fs_x( 'Once', 'as once a year' ),
501
+ 'year' => _fs_x( 'year', 'as annual period' ),
502
+ 'lifetime' => _fs_text( 'Lifetime' ),
503
+ 'best' => _fs_x( 'Best', 'e.g. the best product' ),
504
+ 'billed-x' => _fs_x( 'Billed %s', 'e.g. billed monthly' ),
505
+ 'save-x' => _fs_x( 'Save %s', 'as a discount of $5 or 10%' ),
506
+ #endregion Billing Cycles
507
+ 'view-details' => _fs_text( 'View details' ),
508
+ #--------------------------------------------------------------------------------
509
+ #region Trial
510
+ #--------------------------------------------------------------------------------
511
+ 'approve-start-trial' => _fs_x( 'Approve & Start Trial', 'button label' ),
512
+ /* translators: %1$s: Number of trial days; %2$s: Plan name; */
513
+ 'start-trial-prompt-header' => _fs_text( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.' ),
514
+ /* translators: %s: Link to freemius.com */
515
+ 'start-trial-prompt-message' => _fs_text( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.' ),
516
+
517
+ #endregion
518
+ #--------------------------------------------------------------------------------
519
+ #region Billing Details
520
+ #--------------------------------------------------------------------------------
521
+ 'business-name' => _fs_text( 'Business name' ),
522
+ 'tax-vat-id' => _fs_text( 'Tax / VAT ID' ),
523
+ 'address-line-n' => _fs_text( 'Address Line %d' ),
524
+ 'country' => _fs_text( 'Country' ),
525
+ 'select-country' => _fs_text( 'Select Country' ),
526
+ 'city' => _fs_text( 'City' ),
527
+ 'town' => _fs_text( 'Town' ),
528
+ 'state' => _fs_text( 'State' ),
529
+ 'province' => _fs_text( 'Province' ),
530
+ 'zip-postal-code' => _fs_text( 'ZIP / Postal Code' ),
531
+ #endregion
532
+ #--------------------------------------------------------------------------------
533
+ #region Module Installation
534
+ #--------------------------------------------------------------------------------
535
+ 'installing-plugin-x' => _fs_text( 'Installing plugin: %s' ),
536
+ 'auto-installation' => _fs_text( 'Automatic Installation' ),
537
+ /* translators: %s: Number of seconds */
538
+ 'x-sec' => _fs_text( '%s sec' ),
539
+ 'installing-in-n' => _fs_text( 'An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.' ),
540
+ 'installing-module-x' => _fs_text( 'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.' ),
541
+ 'cancel-installation' => _fs_text( 'Cancel Installation' ),
542
+ 'module-package-rename-failure' => _fs_text( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.' ),
543
+ 'auto-install-error-invalid-id' => _fs_text( 'Invalid module ID.' ),
544
+ 'auto-install-error-not-opted-in' => _fs_text( 'Auto installation only works for opted-in users.' ),
545
+ 'auto-install-error-premium-activated' => _fs_text( 'Premium version already active.' ),
546
+ 'auto-install-error-premium-addon-activated' => _fs_text( 'Premium add-on version already installed.' ),
547
+ 'auto-install-error-invalid-license' => _fs_text( 'You do not have a valid license to access the premium version.' ),
548
+ 'auto-install-error-serviceware' => _fs_text( 'Plugin is a "Serviceware" which means it does not have a premium code version.' ),
549
+ #endregion
550
+
551
+ /* translators: %s: Page name */
552
+ 'secure-x-page-header' => _fs_text( 'Secure HTTPS %s page, running from an external domain' ),
553
+ 'pci-compliant' => _fs_text( 'PCI compliant' ),
554
+ 'view-paid-features' => _fs_text( 'View paid features' ),
555
+ );
556
+
557
+ /**
558
+ * Localization of the strings in the plugin/theme info dialog box.
559
+ *
560
+ * $fs_module_info_text should ONLY include strings that are not located in $fs_text.
561
+ *
562
+ * @author Vova Feldman (@svovaf)
563
+ * @since 1.2.2
564
+ */
565
+ global $fs_module_info_text;
566
+
567
+ $fs_module_info_text = array(
568
+ 'description' => _fs_x( 'Description', 'Plugin installer section title' ),
569
+ 'installation' => _fs_x( 'Installation', 'Plugin installer section title' ),
570
+ 'faq' => _fs_x( 'FAQ', 'Plugin installer section title' ),
571
+ 'changelog' => _fs_x( 'Changelog', 'Plugin installer section title' ),
572
+ 'reviews' => _fs_x( 'Reviews', 'Plugin installer section title' ),
573
+ 'other_notes' => _fs_x( 'Other Notes', 'Plugin installer section title' ),
574
+ /* translators: %s: 1 or One */
575
+ 'x-star' => _fs_text( '%s star' ),
576
+ /* translators: %s: Number larger than 1 */
577
+ 'x-stars' => _fs_text( '%s stars' ),
578
+ /* translators: %s: 1 or One */
579
+ 'x-rating' => _fs_text( '%s rating' ),
580
+ /* translators: %s: Number larger than 1 */
581
+ 'x-ratings' => _fs_text( '%s ratings' ),
582
+ /* translators: %s: 1 or One (Number of times downloaded) */
583
+ 'x-time' => _fs_text( '%s time' ),
584
+ /* translators: %s: Number of times downloaded */
585
+ 'x-times' => _fs_text( '%s times' ),
586
+ /* translators: %s: # of stars (e.g. 5 stars) */
587
+ 'click-to-reviews' => _fs_text( 'Click to see reviews that provided a rating of %s' ),
588
+ 'last-updated:' => _fs_text( 'Last Updated' ),
589
+ 'requires-wordpress-version:' => _fs_text( 'Requires WordPress Version:' ),
590
+ 'author:' => _fs_x( 'Author:', 'as the plugin author' ),
591
+ 'compatible-up-to:' => _fs_text( 'Compatible up to:' ),
592
+ 'downloaded:' => _fs_text( 'Downloaded:' ),
593
+ 'wp-org-plugin-page' => _fs_text( 'WordPress.org Plugin Page' ),
594
+ 'plugin-homepage' => _fs_text( 'Plugin Homepage' ),
595
+ 'donate-to-plugin' => _fs_text( 'Donate to this plugin' ),
596
+ 'average-rating' => _fs_text( 'Average Rating' ),
597
+ 'based-on-x' => _fs_text( 'based on %s' ),
598
+ 'warning:' => _fs_text( 'Warning:' ),
599
+ 'contributors' => _fs_text( 'Contributors' ),
600
+ 'plugin-install' => _fs_text( 'Plugin Install' ),
601
+ 'not-tested-warning' => _fs_text( 'This plugin has not been tested with your current version of WordPress.' ),
602
+ 'not-compatible-warning' => _fs_text( 'This plugin has not been marked as compatible with your version of WordPress.' ),
603
+ 'newer-installed' => _fs_text( 'Newer Version (%s) Installed' ),
604
+ 'latest-installed' => _fs_text( 'Latest Version Installed' ),
605
+ );
freemius/includes/managers/class-fs-admin-notice-manager.php CHANGED
@@ -175,7 +175,12 @@
175
  *
176
  */
177
  function dismiss_notice_ajax_callback() {
178
- $this->_sticky_storage->remove( $_POST['message_id'] );
 
 
 
 
 
179
  wp_die();
180
  }
181
 
@@ -469,4 +474,4 @@
469
  }
470
 
471
  #endregion
472
- }
175
  *
176
  */
177
  function dismiss_notice_ajax_callback() {
178
+ check_admin_referer( 'fs_dismiss_notice_action' );
179
+
180
+ if ( ! is_numeric( $_POST['message_id'] ) ) {
181
+ $this->_sticky_storage->remove( $_POST['message_id'] );
182
+ }
183
+
184
  wp_die();
185
  }
186
 
474
  }
475
 
476
  #endregion
477
+ }
freemius/includes/managers/class-fs-plugin-manager.php CHANGED
@@ -1,220 +1,220 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.0.6
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- class FS_Plugin_Manager {
14
- /**
15
- * @since 1.2.2
16
- *
17
- * @var string|number
18
- */
19
- protected $_module_id;
20
- /**
21
- * @since 1.2.2
22
- *
23
- * @var FS_Plugin
24
- */
25
- protected $_module;
26
-
27
- /**
28
- * @var FS_Plugin_Manager[]
29
- */
30
- private static $_instances = array();
31
- /**
32
- * @var FS_Logger
33
- */
34
- protected $_logger;
35
-
36
- /**
37
- * Option names
38
- *
39
- * @author Leo Fajardo (@leorw)
40
- * @since 1.2.2
41
- */
42
- const OPTION_NAME_PLUGINS = 'plugins';
43
- const OPTION_NAME_THEMES = 'themes';
44
-
45
- /**
46
- * @param string|number $module_id
47
- *
48
- * @return FS_Plugin_Manager
49
- */
50
- static function instance( $module_id ) {
51
- $key = 'm_' . $module_id;
52
-
53
- if ( ! isset( self::$_instances[ $key ] ) ) {
54
- self::$_instances[ $key ] = new FS_Plugin_Manager( $module_id );
55
- }
56
-
57
- return self::$_instances[ $key ];
58
- }
59
-
60
- /**
61
- * @param string|number $module_id
62
- */
63
- protected function __construct( $module_id ) {
64
- $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_' . 'plugins', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
65
- $this->_module_id = $module_id;
66
-
67
- $this->load();
68
- }
69
-
70
- protected function get_option_manager() {
71
- return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, true );
72
- }
73
-
74
- /**
75
- * @author Leo Fajardo (@leorw)
76
- * @since 1.2.2
77
- *
78
- * @param string|bool $module_type "plugin", "theme", or "false" for all modules.
79
- *
80
- * @return array
81
- */
82
- protected function get_all_modules( $module_type = false ) {
83
- $option_manager = $this->get_option_manager();
84
-
85
- if ( false !== $module_type ) {
86
- return fs_get_entities( $option_manager->get_option( $module_type . 's', array() ), FS_Plugin::get_class_name() );
87
- }
88
-
89
- return array(
90
- self::OPTION_NAME_PLUGINS => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_PLUGINS, array() ), FS_Plugin::get_class_name() ),
91
- self::OPTION_NAME_THEMES => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_THEMES, array() ), FS_Plugin::get_class_name() ),
92
- );
93
- }
94
-
95
- /**
96
- * Load plugin data from local DB.
97
- *
98
- * @author Vova Feldman (@svovaf)
99
- * @since 1.0.6
100
- */
101
- function load() {
102
- $all_modules = $this->get_all_modules();
103
-
104
- if ( ! is_numeric( $this->_module_id ) ) {
105
- unset( $all_modules[ self::OPTION_NAME_THEMES ] );
106
- }
107
-
108
- foreach ( $all_modules as $modules ) {
109
- /**
110
- * @since 1.2.2
111
- *
112
- * @var $modules FS_Plugin[]
113
- */
114
- foreach ( $modules as $module ) {
115
- $found_module = false;
116
-
117
- /**
118
- * If module ID is not numeric, it must be a plugin's slug.
119
- *
120
- * @author Leo Fajardo (@leorw)
121
- * @since 1.2.2
122
- */
123
- if ( ! is_numeric( $this->_module_id ) ) {
124
- if ( $this->_module_id === $module->slug ) {
125
- $this->_module_id = $module->id;
126
- $found_module = true;
127
- }
128
- } else if ( $this->_module_id == $module->id ) {
129
- $found_module = true;
130
- }
131
-
132
- if ( $found_module ) {
133
- $this->_module = $module;
134
- break;
135
- }
136
- }
137
- }
138
- }
139
-
140
- /**
141
- * Store plugin on local DB.
142
- *
143
- * @author Vova Feldman (@svovaf)
144
- * @since 1.0.6
145
- *
146
- * @param bool|FS_Plugin $module
147
- * @param bool $flush
148
- *
149
- * @return bool|\FS_Plugin
150
- */
151
- function store( $module = false, $flush = true ) {
152
- if ( false !== $module ) {
153
- $this->_module = $module;
154
- }
155
-
156
- $all_modules = $this->get_all_modules( $this->_module->type );
157
- $all_modules[ $this->_module->slug ] = $this->_module;
158
-
159
- $options_manager = $this->get_option_manager();
160
- $options_manager->set_option( $this->_module->type . 's', $all_modules, $flush );
161
-
162
- return $this->_module;
163
- }
164
-
165
- /**
166
- * Update local plugin data if different.
167
- *
168
- * @author Vova Feldman (@svovaf)
169
- * @since 1.0.6
170
- *
171
- * @param \FS_Plugin $plugin
172
- * @param bool $store
173
- *
174
- * @return bool True if plugin was updated.
175
- */
176
- function update( FS_Plugin $plugin, $store = true ) {
177
- if ( ! ($this->_module instanceof FS_Plugin ) ||
178
- $this->_module->slug != $plugin->slug ||
179
- $this->_module->public_key != $plugin->public_key ||
180
- $this->_module->secret_key != $plugin->secret_key ||
181
- $this->_module->parent_plugin_id != $plugin->parent_plugin_id ||
182
- $this->_module->title != $plugin->title
183
- ) {
184
- $this->store( $plugin, $store );
185
-
186
- return true;
187
- }
188
-
189
- return false;
190
- }
191
-
192
- /**
193
- * @author Vova Feldman (@svovaf)
194
- * @since 1.0.6
195
- *
196
- * @param FS_Plugin $plugin
197
- * @param bool $store
198
- */
199
- function set( FS_Plugin $plugin, $store = false ) {
200
- $this->_module = $plugin;
201
-
202
- if ( $store ) {
203
- $this->store();
204
- }
205
- }
206
-
207
- /**
208
- * @author Vova Feldman (@svovaf)
209
- * @since 1.0.6
210
- *
211
- * @return bool|\FS_Plugin
212
- */
213
- function get() {
214
- return isset( $this->_module ) ?
215
- $this->_module :
216
- false;
217
- }
218
-
219
-
220
  }
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.0.6
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ class FS_Plugin_Manager {
14
+ /**
15
+ * @since 1.2.2
16
+ *
17
+ * @var string|number
18
+ */
19
+ protected $_module_id;
20
+ /**
21
+ * @since 1.2.2
22
+ *
23
+ * @var FS_Plugin
24
+ */
25
+ protected $_module;
26
+
27
+ /**
28
+ * @var FS_Plugin_Manager[]
29
+ */
30
+ private static $_instances = array();
31
+ /**
32
+ * @var FS_Logger
33
+ */
34
+ protected $_logger;
35
+
36
+ /**
37
+ * Option names
38
+ *
39
+ * @author Leo Fajardo (@leorw)
40
+ * @since 1.2.2
41
+ */
42
+ const OPTION_NAME_PLUGINS = 'plugins';
43
+ const OPTION_NAME_THEMES = 'themes';
44
+
45
+ /**
46
+ * @param string|number $module_id
47
+ *
48
+ * @return FS_Plugin_Manager
49
+ */
50
+ static function instance( $module_id ) {
51
+ $key = 'm_' . $module_id;
52
+
53
+ if ( ! isset( self::$_instances[ $key ] ) ) {
54
+ self::$_instances[ $key ] = new FS_Plugin_Manager( $module_id );
55
+ }
56
+
57
+ return self::$_instances[ $key ];
58
+ }
59
+
60
+ /**
61
+ * @param string|number $module_id
62
+ */
63
+ protected function __construct( $module_id ) {
64
+ $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_' . 'plugins', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
65
+ $this->_module_id = $module_id;
66
+
67
+ $this->load();
68
+ }
69
+
70
+ protected function get_option_manager() {
71
+ return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, true );
72
+ }
73
+
74
+ /**
75
+ * @author Leo Fajardo (@leorw)
76
+ * @since 1.2.2
77
+ *
78
+ * @param string|bool $module_type "plugin", "theme", or "false" for all modules.
79
+ *
80
+ * @return array
81
+ */
82
+ protected function get_all_modules( $module_type = false ) {
83
+ $option_manager = $this->get_option_manager();
84
+
85
+ if ( false !== $module_type ) {
86
+ return fs_get_entities( $option_manager->get_option( $module_type . 's', array() ), FS_Plugin::get_class_name() );
87
+ }
88
+
89
+ return array(
90
+ self::OPTION_NAME_PLUGINS => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_PLUGINS, array() ), FS_Plugin::get_class_name() ),
91
+ self::OPTION_NAME_THEMES => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_THEMES, array() ), FS_Plugin::get_class_name() ),
92
+ );
93
+ }
94
+
95
+ /**
96
+ * Load plugin data from local DB.
97
+ *
98
+ * @author Vova Feldman (@svovaf)
99
+ * @since 1.0.6
100
+ */
101
+ function load() {
102
+ $all_modules = $this->get_all_modules();
103
+
104
+ if ( ! is_numeric( $this->_module_id ) ) {
105
+ unset( $all_modules[ self::OPTION_NAME_THEMES ] );
106
+ }
107
+
108
+ foreach ( $all_modules as $modules ) {
109
+ /**
110
+ * @since 1.2.2
111
+ *
112
+ * @var $modules FS_Plugin[]
113
+ */
114
+ foreach ( $modules as $module ) {
115
+ $found_module = false;
116
+
117
+ /**
118
+ * If module ID is not numeric, it must be a plugin's slug.
119
+ *
120
+ * @author Leo Fajardo (@leorw)
121
+ * @since 1.2.2
122
+ */
123
+ if ( ! is_numeric( $this->_module_id ) ) {
124
+ if ( $this->_module_id === $module->slug ) {
125
+ $this->_module_id = $module->id;
126
+ $found_module = true;
127
+ }
128
+ } else if ( $this->_module_id == $module->id ) {
129
+ $found_module = true;
130
+ }
131
+
132
+ if ( $found_module ) {
133
+ $this->_module = $module;
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Store plugin on local DB.
142
+ *
143
+ * @author Vova Feldman (@svovaf)
144
+ * @since 1.0.6
145
+ *
146
+ * @param bool|FS_Plugin $module
147
+ * @param bool $flush
148
+ *
149
+ * @return bool|\FS_Plugin
150
+ */
151
+ function store( $module = false, $flush = true ) {
152
+ if ( false !== $module ) {
153
+ $this->_module = $module;
154
+ }
155
+
156
+ $all_modules = $this->get_all_modules( $this->_module->type );
157
+ $all_modules[ $this->_module->slug ] = $this->_module;
158
+
159
+ $options_manager = $this->get_option_manager();
160
+ $options_manager->set_option( $this->_module->type . 's', $all_modules, $flush );
161
+
162
+ return $this->_module;
163
+ }
164
+
165
+ /**
166
+ * Update local plugin data if different.
167
+ *
168
+ * @author Vova Feldman (@svovaf)
169
+ * @since 1.0.6
170
+ *
171
+ * @param \FS_Plugin $plugin
172
+ * @param bool $store
173
+ *
174
+ * @return bool True if plugin was updated.
175
+ */
176
+ function update( FS_Plugin $plugin, $store = true ) {
177
+ if ( ! ($this->_module instanceof FS_Plugin ) ||
178
+ $this->_module->slug != $plugin->slug ||
179
+ $this->_module->public_key != $plugin->public_key ||
180
+ $this->_module->secret_key != $plugin->secret_key ||
181
+ $this->_module->parent_plugin_id != $plugin->parent_plugin_id ||
182
+ $this->_module->title != $plugin->title
183
+ ) {
184
+ $this->store( $plugin, $store );
185
+
186
+ return true;
187
+ }
188
+
189
+ return false;
190
+ }
191
+
192
+ /**
193
+ * @author Vova Feldman (@svovaf)
194
+ * @since 1.0.6
195
+ *
196
+ * @param FS_Plugin $plugin
197
+ * @param bool $store
198
+ */
199
+ function set( FS_Plugin $plugin, $store = false ) {
200
+ $this->_module = $plugin;
201
+
202
+ if ( $store ) {
203
+ $this->store();
204
+ }
205
+ }
206
+
207
+ /**
208
+ * @author Vova Feldman (@svovaf)
209
+ * @since 1.0.6
210
+ *
211
+ * @return bool|\FS_Plugin
212
+ */
213
+ function get() {
214
+ return isset( $this->_module ) ?
215
+ $this->_module :
216
+ false;
217
+ }
218
+
219
+
220
  }
freemius/includes/sdk/Exceptions/ArgumentNotExistException.php CHANGED
@@ -1,4 +1,8 @@
1
  <?php
 
 
 
 
2
  if ( ! class_exists( 'Freemius_InvalidArgumentException' ) ) {
3
  exit;
4
  }
@@ -6,4 +10,4 @@
6
  if ( ! class_exists( 'Freemius_ArgumentNotExistException' ) ) {
7
  class Freemius_ArgumentNotExistException extends Freemius_InvalidArgumentException {
8
  }
9
- }
1
  <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
  if ( ! class_exists( 'Freemius_InvalidArgumentException' ) ) {
7
  exit;
8
  }
10
  if ( ! class_exists( 'Freemius_ArgumentNotExistException' ) ) {
11
  class Freemius_ArgumentNotExistException extends Freemius_InvalidArgumentException {
12
  }
13
+ }
freemius/includes/sdk/Exceptions/EmptyArgumentException.php CHANGED
@@ -1,4 +1,8 @@
1
  <?php
 
 
 
 
2
  if ( ! class_exists( 'Freemius_InvalidArgumentException' ) ) {
3
  exit;
4
  }
@@ -6,4 +10,4 @@
6
  if ( ! class_exists( 'Freemius_EmptyArgumentException' ) ) {
7
  class Freemius_EmptyArgumentException extends Freemius_InvalidArgumentException {
8
  }
9
- }
1
  <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
  if ( ! class_exists( 'Freemius_InvalidArgumentException' ) ) {
7
  exit;
8
  }
10
  if ( ! class_exists( 'Freemius_EmptyArgumentException' ) ) {
11
  class Freemius_EmptyArgumentException extends Freemius_InvalidArgumentException {
12
  }
13
+ }
freemius/includes/sdk/Exceptions/Exception.php CHANGED
@@ -1,4 +1,8 @@
1
  <?php
 
 
 
 
2
  if ( ! class_exists( 'Freemius_Exception' ) ) {
3
  /**
4
  * Thrown when an API call returns an exception.
@@ -71,4 +75,4 @@
71
  return $str . $this->getMessage();
72
  }
73
  }
74
- }
1
  <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
  if ( ! class_exists( 'Freemius_Exception' ) ) {
7
  /**
8
  * Thrown when an API call returns an exception.
75
  return $str . $this->getMessage();
76
  }
77
  }
78
+ }
freemius/includes/sdk/Exceptions/InvalidArgumentException.php CHANGED
@@ -1,8 +1,12 @@
1
  <?php
 
 
 
 
2
  if ( ! class_exists( 'Freemius_Exception' ) ) {
3
  exit;
4
  }
5
 
6
  if ( ! class_exists( 'Freemius_InvalidArgumentException' ) ) {
7
  class Freemius_InvalidArgumentException extends Freemius_Exception { }
8
- }
1
  <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
  if ( ! class_exists( 'Freemius_Exception' ) ) {
7
  exit;
8
  }
9
 
10
  if ( ! class_exists( 'Freemius_InvalidArgumentException' ) ) {
11
  class Freemius_InvalidArgumentException extends Freemius_Exception { }
12
+ }
freemius/includes/sdk/Exceptions/OAuthException.php CHANGED
@@ -1,4 +1,8 @@
1
  <?php
 
 
 
 
2
  if ( ! class_exists( 'Freemius_Exception' ) ) {
3
  exit;
4
  }
@@ -9,4 +13,4 @@
9
  parent::__construct( $pResult );
10
  }
11
  }
12
- }
1
  <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
  if ( ! class_exists( 'Freemius_Exception' ) ) {
7
  exit;
8
  }
13
  parent::__construct( $pResult );
14
  }
15
  }
16
+ }
freemius/includes/sdk/FreemiusBase.php CHANGED
@@ -15,6 +15,10 @@
15
  * under the License.
16
  */
17
 
 
 
 
 
18
  if ( ! defined( 'FS_API__VERSION' ) ) {
19
  define( 'FS_API__VERSION', '1' );
20
  }
15
  * under the License.
16
  */
17
 
18
+ if ( ! defined( 'ABSPATH' ) ) {
19
+ exit;
20
+ }
21
+
22
  if ( ! defined( 'FS_API__VERSION' ) ) {
23
  define( 'FS_API__VERSION', '1' );
24
  }
freemius/includes/sdk/FreemiusWordPress.php CHANGED
@@ -14,6 +14,9 @@
14
  * License for the specific language governing permissions and limitations
15
  * under the License.
16
  */
 
 
 
17
 
18
  require_once dirname( __FILE__ ) . '/FreemiusBase.php';
19
 
@@ -709,4 +712,4 @@
709
  }
710
 
711
  #endregion
712
- }
14
  * License for the specific language governing permissions and limitations
15
  * under the License.
16
  */
17
+ if ( ! defined( 'ABSPATH' ) ) {
18
+ exit;
19
+ }
20
 
21
  require_once dirname( __FILE__ ) . '/FreemiusBase.php';
22
 
712
  }
713
 
714
  #endregion
715
+ }
freemius/languages/freemius-cs_CZ.mo CHANGED
Binary file
freemius/languages/freemius-da_DK.mo CHANGED
Binary file
freemius/languages/freemius-en.mo CHANGED
Binary file
freemius/languages/freemius-es_ES.mo CHANGED
Binary file
freemius/languages/freemius-fr_FR.mo CHANGED
Binary file
freemius/languages/freemius-he_IL.mo CHANGED
Binary file
freemius/languages/freemius-hu_HU.mo CHANGED
Binary file
freemius/languages/freemius-it_IT.mo CHANGED
Binary file
freemius/languages/freemius-ja.mo CHANGED
Binary file
freemius/languages/freemius-nl_NL.mo CHANGED
Binary file
freemius/languages/freemius-ru_RU.mo CHANGED
Binary file
freemius/languages/freemius-ta.mo CHANGED
Binary file
freemius/languages/freemius-zh_CN.mo CHANGED
Binary file
freemius/languages/freemius.pot CHANGED
@@ -32,787 +32,779 @@ msgstr ""
32
  msgid "Error"
33
  msgstr ""
34
 
35
- #: includes/class-freemius.php:2544
36
  msgid "I found a better %s"
37
  msgstr ""
38
 
39
- #: includes/class-freemius.php:2546
40
  msgid "What's the %s's name?"
41
  msgstr ""
42
 
43
- #: includes/class-freemius.php:2552
44
  msgid "It's a temporary %s. I'm just debugging an issue."
45
  msgstr ""
46
 
47
- #: includes/class-freemius.php:2554
48
  msgid "Deactivation"
49
  msgstr ""
50
 
51
- #: includes/class-freemius.php:2555
52
  msgid "Theme Switch"
53
  msgstr ""
54
 
55
- #: includes/class-freemius.php:2564, templates/forms/resend-key.php:24, templates/forms/user-change.php:29
56
  msgid "Other"
57
  msgstr ""
58
 
59
- #: includes/class-freemius.php:2572
60
  msgid "I no longer need the %s"
61
  msgstr ""
62
 
63
- #: includes/class-freemius.php:2579
64
  msgid "I only needed the %s for a short period"
65
  msgstr ""
66
 
67
- #: includes/class-freemius.php:2585
68
  msgid "The %s broke my site"
69
  msgstr ""
70
 
71
- #: includes/class-freemius.php:2592
72
  msgid "The %s suddenly stopped working"
73
  msgstr ""
74
 
75
- #: includes/class-freemius.php:2602
76
  msgid "I can't pay for it anymore"
77
  msgstr ""
78
 
79
- #: includes/class-freemius.php:2604
80
  msgid "What price would you feel comfortable paying?"
81
  msgstr ""
82
 
83
- #: includes/class-freemius.php:2610
84
  msgid "I don't like to share my information with you"
85
  msgstr ""
86
 
87
- #: includes/class-freemius.php:2631
88
  msgid "The %s didn't work"
89
  msgstr ""
90
 
91
- #: includes/class-freemius.php:2641
92
  msgid "I couldn't understand how to make it work"
93
  msgstr ""
94
 
95
- #: includes/class-freemius.php:2649
96
  msgid "The %s is great, but I need specific feature that you don't support"
97
  msgstr ""
98
 
99
- #: includes/class-freemius.php:2651
100
  msgid "What feature?"
101
  msgstr ""
102
 
103
- #: includes/class-freemius.php:2655
104
  msgid "The %s is not working"
105
  msgstr ""
106
 
107
- #: includes/class-freemius.php:2657
108
  msgid "Kindly share what didn't work so we can fix it for future users..."
109
  msgstr ""
110
 
111
- #: includes/class-freemius.php:2661
112
  msgid "It's not what I was looking for"
113
  msgstr ""
114
 
115
- #: includes/class-freemius.php:2663
116
  msgid "What you've been looking for?"
117
  msgstr ""
118
 
119
- #: includes/class-freemius.php:2667
120
  msgid "The %s didn't work as expected"
121
  msgstr ""
122
 
123
- #: includes/class-freemius.php:2669
124
  msgid "What did you expect?"
125
  msgstr ""
126
 
127
- #: includes/class-freemius.php:3524, templates/debug.php:20
128
  msgid "Freemius Debug"
129
  msgstr ""
130
 
131
- #: includes/class-freemius.php:4276
132
  msgid "I don't know what is cURL or how to install it, help me!"
133
  msgstr ""
134
 
135
- #: includes/class-freemius.php:4278
136
  msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update."
137
  msgstr ""
138
 
139
- #: includes/class-freemius.php:4285
140
  msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again."
141
  msgstr ""
142
 
143
- #: includes/class-freemius.php:4390
144
  msgid "Yes - do your thing"
145
  msgstr ""
146
 
147
- #: includes/class-freemius.php:4395
148
  msgid "No - just deactivate"
149
  msgstr ""
150
 
151
- #: includes/class-freemius.php:4440, includes/class-freemius.php:4956, includes/class-freemius.php:6217, includes/class-freemius.php:13397, includes/class-freemius.php:14139, includes/class-freemius.php:17571, includes/class-freemius.php:17676, includes/class-freemius.php:17851, includes/class-freemius.php:20085, includes/class-freemius.php:20443, includes/class-freemius.php:20453, includes/class-freemius.php:21138, includes/class-freemius.php:22046, includes/class-freemius.php:22179, includes/class-freemius.php:22335, templates/add-ons.php:57
152
  msgctxt "exclamation"
153
  msgid "Oops"
154
  msgstr ""
155
 
156
- #: includes/class-freemius.php:4509
157
  msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience."
158
  msgstr ""
159
 
160
- #: includes/class-freemius.php:4925
161
- msgid "You have purchased a %s license."
162
- msgstr ""
163
-
164
- #: includes/class-freemius.php:4929
165
- msgid " The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box."
166
- msgstr ""
167
-
168
- #: includes/class-freemius.php:4939, includes/class-freemius.php:5914, includes/class-freemius.php:16976, includes/class-freemius.php:16987, includes/class-freemius.php:20354, includes/class-freemius.php:20718, includes/class-freemius.php:20787, includes/class-freemius.php:20959
169
- msgctxt "interjection expressing joy or exuberance"
170
- msgid "Yee-haw"
171
- msgstr ""
172
-
173
- #: includes/class-freemius.php:4953
174
  msgctxt "addonX cannot run without pluginY"
175
  msgid "%s cannot run without %s."
176
  msgstr ""
177
 
178
- #: includes/class-freemius.php:4954
179
  msgctxt "addonX cannot run..."
180
  msgid "%s cannot run without the plugin."
181
  msgstr ""
182
 
183
- #: includes/class-freemius.php:5153, includes/class-freemius.php:5178, includes/class-freemius.php:21209
184
  msgid "Unexpected API error. Please contact the %s's author with the following error."
185
  msgstr ""
186
 
187
- #: includes/class-freemius.php:5883
188
  msgid "Premium %s version was successfully activated."
189
  msgstr ""
190
 
191
- #: includes/class-freemius.php:5895, includes/class-freemius.php:7798
192
  msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)."
193
  msgid "W00t"
194
  msgstr ""
195
 
196
- #: includes/class-freemius.php:5910
197
  msgid "You have a %s license."
198
  msgstr ""
199
 
200
- #: includes/class-freemius.php:6200
 
 
 
 
 
201
  msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license."
202
  msgstr ""
203
 
204
- #: includes/class-freemius.php:6204
205
  msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin."
206
  msgstr ""
207
 
208
- #: includes/class-freemius.php:6213, templates/add-ons.php:186, templates/account/partials/addon.php:381
209
  msgid "More information about %s"
210
  msgstr ""
211
 
212
- #: includes/class-freemius.php:6214
213
  msgid "Purchase License"
214
  msgstr ""
215
 
216
- #: includes/class-freemius.php:7151, templates/connect.php:180
217
  msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s."
218
  msgstr ""
219
 
220
- #: includes/class-freemius.php:7155
221
  msgid "start the trial"
222
  msgstr ""
223
 
224
- #: includes/class-freemius.php:7156, templates/connect.php:184
225
  msgid "complete the install"
226
  msgstr ""
227
 
228
- #: includes/class-freemius.php:7275
229
  msgid "You are just one step away - %s"
230
  msgstr ""
231
 
232
- #: includes/class-freemius.php:7278
233
  msgctxt "%s - plugin name. As complete \"PluginX\" activation now"
234
  msgid "Complete \"%s\" Activation Now"
235
  msgstr ""
236
 
237
- #: includes/class-freemius.php:7360
238
  msgid "We made a few tweaks to the %s, %s"
239
  msgstr ""
240
 
241
- #: includes/class-freemius.php:7364
242
  msgid "Opt in to make \"%s\" better!"
243
  msgstr ""
244
 
245
- #: includes/class-freemius.php:7797
246
  msgid "The upgrade of %s was successfully completed."
247
  msgstr ""
248
 
249
- #: includes/class-freemius.php:10279, includes/class-fs-plugin-updater.php:1089, includes/class-fs-plugin-updater.php:1284, includes/class-fs-plugin-updater.php:1291, templates/auto-installation.php:32
250
  msgid "Add-On"
251
  msgstr ""
252
 
253
- #: includes/class-freemius.php:10281, templates/account.php:394, templates/account.php:402, templates/debug.php:358, templates/debug.php:549
254
  msgid "Plugin"
255
  msgstr ""
256
 
257
- #: includes/class-freemius.php:10282, templates/account.php:395, templates/account.php:403, templates/debug.php:358, templates/debug.php:549, templates/forms/deactivation/form.php:71
258
  msgid "Theme"
259
  msgstr ""
260
 
261
- #: includes/class-freemius.php:13217
262
  msgid "An unknown error has occurred while trying to toggle the license's white-label mode."
263
  msgstr ""
264
 
265
- #: includes/class-freemius.php:13231
266
  msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s."
267
  msgstr ""
268
 
269
- #: includes/class-freemius.php:13236
270
  msgid "User Dashboard"
271
  msgstr ""
272
 
273
- #: includes/class-freemius.php:13237
274
  msgid "revert it now"
275
  msgstr ""
276
 
277
- #: includes/class-freemius.php:13295
278
  msgid "An unknown error has occurred while trying to set the user's beta mode."
279
  msgstr ""
280
 
281
- #: includes/class-freemius.php:13368
282
  msgid "Invalid new user ID or email address."
283
  msgstr ""
284
 
285
- #: includes/class-freemius.php:13398, includes/class-freemius.php:22290
286
  msgid "Sorry, we could not complete the email update. Another user with the same email is already registered."
287
  msgstr ""
288
 
289
- #: includes/class-freemius.php:13399, includes/class-freemius.php:22291
290
  msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button."
291
  msgstr ""
292
 
293
- #: includes/class-freemius.php:13406, includes/class-freemius.php:22298
294
  msgid "Change Ownership"
295
  msgstr ""
296
 
297
- #: includes/class-freemius.php:14006
298
  msgid "Invalid site details collection."
299
  msgstr ""
300
 
301
- #: includes/class-freemius.php:14126
302
  msgid "We couldn't find your email address in the system, are you sure it's the right address?"
303
  msgstr ""
304
 
305
- #: includes/class-freemius.php:14128
306
  msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?"
307
  msgstr ""
308
 
309
- #: includes/class-freemius.php:14402
310
  msgid "Account is pending activation."
311
  msgstr ""
312
 
313
- #: includes/class-freemius.php:14514, templates/forms/premium-versions-upgrade-handler.php:47
314
  msgid "Buy a license now"
315
  msgstr ""
316
 
317
- #: includes/class-freemius.php:14526, templates/forms/premium-versions-upgrade-handler.php:46
318
  msgid "Renew your license now"
319
  msgstr ""
320
 
321
- #: includes/class-freemius.php:14530
322
  msgid "%s to access version %s security & feature updates, and support."
323
  msgstr ""
324
 
325
- #: includes/class-freemius.php:16958
326
  msgid "%s activation was successfully completed."
327
  msgstr ""
328
 
329
- #: includes/class-freemius.php:16972
330
  msgid "Your account was successfully activated with the %s plan."
331
  msgstr ""
332
 
333
- #: includes/class-freemius.php:16983, includes/class-freemius.php:20783
334
  msgid "Your trial has been successfully started."
335
  msgstr ""
336
 
337
- #: includes/class-freemius.php:17569, includes/class-freemius.php:17674, includes/class-freemius.php:17849
338
  msgid "Couldn't activate %s."
339
  msgstr ""
340
 
341
- #: includes/class-freemius.php:17570, includes/class-freemius.php:17675, includes/class-freemius.php:17850
342
  msgid "Please contact us with the following message:"
343
  msgstr ""
344
 
345
- #: includes/class-freemius.php:17671, templates/forms/data-debug-mode.php:162
346
  msgid "An unknown error has occurred."
347
  msgstr ""
348
 
349
- #: includes/class-freemius.php:18207, includes/class-freemius.php:23371
350
  msgid "Upgrade"
351
  msgstr ""
352
 
353
- #: includes/class-freemius.php:18213
354
  msgid "Start Trial"
355
  msgstr ""
356
 
357
- #: includes/class-freemius.php:18215
358
  msgid "Pricing"
359
  msgstr ""
360
 
361
- #: includes/class-freemius.php:18295, includes/class-freemius.php:18297
362
  msgid "Affiliation"
363
  msgstr ""
364
 
365
- #: includes/class-freemius.php:18325, includes/class-freemius.php:18327, templates/account.php:242, templates/debug.php:324
366
  msgid "Account"
367
  msgstr ""
368
 
369
- #: includes/class-freemius.php:18341, includes/class-freemius.php:18343, includes/customizer/class-fs-customizer-support-section.php:60
370
  msgid "Contact Us"
371
  msgstr ""
372
 
373
- #: includes/class-freemius.php:18354, includes/class-freemius.php:18356, includes/class-freemius.php:23385, templates/account.php:121, templates/account/partials/addon.php:44
374
  msgid "Add-Ons"
375
  msgstr ""
376
 
377
- #: includes/class-freemius.php:18390
378
  msgctxt "ASCII arrow left icon"
379
  msgid "&#x2190;"
380
  msgstr ""
381
 
382
- #: includes/class-freemius.php:18390
383
  msgctxt "ASCII arrow right icon"
384
  msgid "&#x27a4;"
385
  msgstr ""
386
 
387
- #: includes/class-freemius.php:18392, templates/pricing.php:109
388
  msgctxt "noun"
389
  msgid "Pricing"
390
  msgstr ""
391
 
392
- #: includes/class-freemius.php:18605, includes/customizer/class-fs-customizer-support-section.php:67
393
  msgid "Support Forum"
394
  msgstr ""
395
 
396
- #: includes/class-freemius.php:19579
397
  msgid "Your email has been successfully verified - you are AWESOME!"
398
  msgstr ""
399
 
400
- #: includes/class-freemius.php:19580
401
  msgctxt "a positive response"
402
  msgid "Right on"
403
  msgstr ""
404
 
405
- #: includes/class-freemius.php:20086
406
  msgid "seems like the key you entered doesn't match our records."
407
  msgstr ""
408
 
409
- #: includes/class-freemius.php:20110
410
  msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link."
411
  msgstr ""
412
 
413
- #: includes/class-freemius.php:20345
414
  msgid "Your %s Add-on plan was successfully upgraded."
415
  msgstr ""
416
 
417
- #: includes/class-freemius.php:20347
418
  msgid "%s Add-on was successfully purchased."
419
  msgstr ""
420
 
421
- #: includes/class-freemius.php:20350
422
  msgid "Download the latest version"
423
  msgstr ""
424
 
425
- #: includes/class-freemius.php:20436
426
  msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s"
427
  msgstr ""
428
 
429
- #: includes/class-freemius.php:20442, includes/class-freemius.php:20452, includes/class-freemius.php:20918, includes/class-freemius.php:21007
430
  msgid "Error received from the server:"
431
  msgstr ""
432
 
433
- #: includes/class-freemius.php:20452
434
  msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again."
435
  msgstr ""
436
 
437
- #: includes/class-freemius.php:20680, includes/class-freemius.php:20923, includes/class-freemius.php:20978, includes/class-freemius.php:21085
438
  msgctxt "something somebody says when they are thinking about what you have just said."
439
  msgid "Hmm"
440
  msgstr ""
441
 
442
- #: includes/class-freemius.php:20693
443
  msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry."
444
  msgstr ""
445
 
446
- #: includes/class-freemius.php:20694, templates/account.php:123, templates/add-ons.php:250, templates/account/partials/addon.php:46
447
  msgctxt "trial period"
448
  msgid "Trial"
449
  msgstr ""
450
 
451
- #: includes/class-freemius.php:20699
452
  msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s."
453
  msgstr ""
454
 
455
- #: includes/class-freemius.php:20703, includes/class-freemius.php:20762
456
  msgid "Please contact us here"
457
  msgstr ""
458
 
459
- #: includes/class-freemius.php:20714
460
  msgid "Your plan was successfully activated."
461
  msgstr ""
462
 
463
- #: includes/class-freemius.php:20715
464
  msgid "Your plan was successfully upgraded."
465
  msgstr ""
466
 
467
- #: includes/class-freemius.php:20732
468
  msgid "Your plan was successfully changed to %s."
469
  msgstr ""
470
 
471
- #: includes/class-freemius.php:20748
472
  msgid "Your license has expired. You can still continue using the free %s forever."
473
  msgstr ""
474
 
475
- #: includes/class-freemius.php:20750
476
  msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions."
477
  msgstr ""
478
 
479
- #: includes/class-freemius.php:20758
480
  msgid "Your license has been cancelled. If you think it's a mistake, please contact support."
481
  msgstr ""
482
 
483
- #: includes/class-freemius.php:20771
484
  msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support."
485
  msgstr ""
486
 
487
- #: includes/class-freemius.php:20797
488
  msgid "Your free trial has expired. You can still continue using all our free features."
489
  msgstr ""
490
 
491
- #: includes/class-freemius.php:20799
492
  msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions."
493
  msgstr ""
494
 
495
- #: includes/class-freemius.php:20914
496
  msgid "It looks like the license could not be activated."
497
  msgstr ""
498
 
499
- #: includes/class-freemius.php:20956
500
  msgid "Your license was successfully activated."
501
  msgstr ""
502
 
503
- #: includes/class-freemius.php:20982
504
  msgid "It looks like your site currently doesn't have an active license."
505
  msgstr ""
506
 
507
- #: includes/class-freemius.php:21006
508
  msgid "It looks like the license deactivation failed."
509
  msgstr ""
510
 
511
- #: includes/class-freemius.php:21035
512
  msgid "Your %s license was successfully deactivated."
513
  msgstr ""
514
 
515
- #: includes/class-freemius.php:21036
516
  msgid "Your license was successfully deactivated, you are back to the %s plan."
517
  msgstr ""
518
 
519
- #: includes/class-freemius.php:21039
520
  msgid "O.K"
521
  msgstr ""
522
 
523
- #: includes/class-freemius.php:21092
524
  msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes."
525
  msgstr ""
526
 
527
- #: includes/class-freemius.php:21101
528
  msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s."
529
  msgstr ""
530
 
531
- #: includes/class-freemius.php:21143
532
  msgid "You are already running the %s in a trial mode."
533
  msgstr ""
534
 
535
- #: includes/class-freemius.php:21154
536
  msgid "You already utilized a trial before."
537
  msgstr ""
538
 
539
- #: includes/class-freemius.php:21168
540
  msgid "Plan %s do not exist, therefore, can't start a trial."
541
  msgstr ""
542
 
543
- #: includes/class-freemius.php:21179
544
  msgid "Plan %s does not support a trial period."
545
  msgstr ""
546
 
547
- #: includes/class-freemius.php:21190
548
  msgid "None of the %s's plans supports a trial period."
549
  msgstr ""
550
 
551
- #: includes/class-freemius.php:21240
552
  msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)"
553
  msgstr ""
554
 
555
- #: includes/class-freemius.php:21276
556
  msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes."
557
  msgstr ""
558
 
559
- #: includes/class-freemius.php:21295
560
  msgid "Your %s free trial was successfully cancelled."
561
  msgstr ""
562
 
563
- #: includes/class-freemius.php:21613
564
  msgid "Version %s was released."
565
  msgstr ""
566
 
567
- #: includes/class-freemius.php:21613
568
  msgid "Please download %s."
569
  msgstr ""
570
 
571
- #: includes/class-freemius.php:21620
572
  msgid "the latest %s version here"
573
  msgstr ""
574
 
575
- #: includes/class-freemius.php:21625
576
  msgid "New"
577
  msgstr ""
578
 
579
- #: includes/class-freemius.php:21630
580
  msgid "Seems like you got the latest release."
581
  msgstr ""
582
 
583
- #: includes/class-freemius.php:21631
584
  msgid "You are all good!"
585
  msgstr ""
586
 
587
- #: includes/class-freemius.php:21934
588
  msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box."
589
  msgstr ""
590
 
591
- #: includes/class-freemius.php:22074
592
  msgid "Site successfully opted in."
593
  msgstr ""
594
 
595
- #: includes/class-freemius.php:22075, includes/class-freemius.php:23081
596
  msgid "Awesome"
597
  msgstr ""
598
 
599
- #: includes/class-freemius.php:22091, templates/forms/optout.php:41
600
  msgid "We appreciate your help in making the %s better by letting us track some usage data."
601
  msgstr ""
602
 
603
- #: includes/class-freemius.php:22092
604
  msgid "Thank you!"
605
  msgstr ""
606
 
607
- #: includes/class-freemius.php:22099
608
  msgid "We will no longer be sending any usage data of %s on %s to %s."
609
  msgstr ""
610
 
611
- #: includes/class-freemius.php:22257
612
  msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder."
613
  msgstr ""
614
 
615
- #: includes/class-freemius.php:22263
616
  msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval."
617
  msgstr ""
618
 
619
- #: includes/class-freemius.php:22268
620
  msgid "%s is the new owner of the account."
621
  msgstr ""
622
 
623
- #: includes/class-freemius.php:22270
624
  msgctxt "as congratulations"
625
  msgid "Congrats"
626
  msgstr ""
627
 
628
- #: includes/class-freemius.php:22306
629
  msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments."
630
  msgstr ""
631
 
632
- #: includes/class-freemius.php:22318
633
  msgid "Please provide your full name."
634
  msgstr ""
635
 
636
- #: includes/class-freemius.php:22323
637
  msgid "Your name was successfully updated."
638
  msgstr ""
639
 
640
- #: includes/class-freemius.php:22384
641
  msgid "You have successfully updated your %s."
642
  msgstr ""
643
 
644
- #: includes/class-freemius.php:22443
645
  msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin."
646
  msgstr ""
647
 
648
- #: includes/class-freemius.php:22446
649
  msgid "Click here"
650
  msgstr ""
651
 
652
- #: includes/class-freemius.php:22544
653
  msgid "Just letting you know that the add-ons information of %s is being pulled from an external server."
654
  msgstr ""
655
 
656
- #: includes/class-freemius.php:22545
657
  msgctxt "advance notice of something that will need attention."
658
  msgid "Heads up"
659
  msgstr ""
660
 
661
- #: includes/class-freemius.php:23121
662
  msgctxt "exclamation"
663
  msgid "Hey"
664
  msgstr ""
665
 
666
- #: includes/class-freemius.php:23121
667
  msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial."
668
  msgstr ""
669
 
670
- #: includes/class-freemius.php:23129
671
  msgid "No commitment for %s days - cancel anytime!"
672
  msgstr ""
673
 
674
- #: includes/class-freemius.php:23130
675
  msgid "No credit card required"
676
  msgstr ""
677
 
678
- #: includes/class-freemius.php:23137, templates/forms/trial-start.php:53
679
  msgctxt "call to action"
680
  msgid "Start free trial"
681
  msgstr ""
682
 
683
- #: includes/class-freemius.php:23214
684
  msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!"
685
  msgstr ""
686
 
687
- #: includes/class-freemius.php:23223
688
  msgid "Learn more"
689
  msgstr ""
690
 
691
- #: includes/class-freemius.php:23409, templates/account.php:558, templates/account.php:708, templates/connect.php:188, templates/connect.php:470, templates/forms/license-activation.php:27, templates/account/partials/addon.php:321
692
  msgid "Activate License"
693
  msgstr ""
694
 
695
- #: includes/class-freemius.php:23410, templates/account.php:652, templates/account.php:707, templates/account/partials/addon.php:322, templates/account/partials/site.php:271
696
  msgid "Change License"
697
  msgstr ""
698
 
699
- #: includes/class-freemius.php:23531, templates/account/partials/site.php:169
700
  msgid "Opt Out"
701
  msgstr ""
702
 
703
- #: includes/class-freemius.php:23533, includes/class-freemius.php:23539, templates/account/partials/site.php:49, templates/account/partials/site.php:169
704
  msgid "Opt In"
705
  msgstr ""
706
 
707
- #: includes/class-freemius.php:23769
708
  msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"
709
  msgstr ""
710
 
711
- #: includes/class-freemius.php:23777
712
  msgid "Activate %s features"
713
  msgstr ""
714
 
715
- #: includes/class-freemius.php:23790
716
  msgid "Please follow these steps to complete the upgrade"
717
  msgstr ""
718
 
719
- #: includes/class-freemius.php:23794
720
  msgid "Download the latest %s version"
721
  msgstr ""
722
 
723
- #: includes/class-freemius.php:23798
724
  msgid "Upload and activate the downloaded version"
725
  msgstr ""
726
 
727
- #: includes/class-freemius.php:23800
728
  msgid "How to upload and activate?"
729
  msgstr ""
730
 
731
- #: includes/class-freemius.php:23934
732
  msgid "%sClick here%s to choose the sites where you'd like to activate the license on."
733
  msgstr ""
734
 
735
- #: includes/class-freemius.php:24103
736
  msgid "Auto installation only works for opted-in users."
737
  msgstr ""
738
 
739
- #: includes/class-freemius.php:24113, includes/class-freemius.php:24146, includes/class-fs-plugin-updater.php:1263, includes/class-fs-plugin-updater.php:1277
740
  msgid "Invalid module ID."
741
  msgstr ""
742
 
743
- #: includes/class-freemius.php:24122, includes/class-fs-plugin-updater.php:1299
744
  msgid "Premium version already active."
745
  msgstr ""
746
 
747
- #: includes/class-freemius.php:24129
748
  msgid "You do not have a valid license to access the premium version."
749
  msgstr ""
750
 
751
- #: includes/class-freemius.php:24136
752
  msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version."
753
  msgstr ""
754
 
755
- #: includes/class-freemius.php:24154, includes/class-fs-plugin-updater.php:1298
756
  msgid "Premium add-on version already installed."
757
  msgstr ""
758
 
759
- #: includes/class-freemius.php:24504
760
  msgid "View paid features"
761
  msgstr ""
762
 
763
- #: includes/class-freemius.php:24826
764
  msgid "Thank you so much for using %s and its add-ons!"
765
  msgstr ""
766
 
767
- #: includes/class-freemius.php:24827
768
  msgid "Thank you so much for using %s!"
769
  msgstr ""
770
 
771
- #: includes/class-freemius.php:24833
772
  msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s."
773
  msgstr ""
774
 
775
- #: includes/class-freemius.php:24837
776
  msgid "Thank you so much for using our products!"
777
  msgstr ""
778
 
779
- #: includes/class-freemius.php:24838
780
  msgid "You've already opted-in to our usage-tracking, which helps us keep improving them."
781
  msgstr ""
782
 
783
- #: includes/class-freemius.php:24857
784
  msgid "%s and its add-ons"
785
  msgstr ""
786
 
787
- #: includes/class-freemius.php:24866
788
  msgid "Products"
789
  msgstr ""
790
 
791
- #: includes/class-freemius.php:24873, templates/connect.php:284
792
  msgid "Yes"
793
  msgstr ""
794
 
795
- #: includes/class-freemius.php:24874, templates/connect.php:285
796
  msgid "send me security & feature updates, educational content and offers."
797
  msgstr ""
798
 
799
- #: includes/class-freemius.php:24875, templates/connect.php:290
800
  msgid "No"
801
  msgstr ""
802
 
803
- #: includes/class-freemius.php:24877, templates/connect.php:292
804
  msgid "do %sNOT%s send me security & feature updates, educational content and offers."
805
  msgstr ""
806
 
807
- #: includes/class-freemius.php:24887
808
  msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)"
809
  msgstr ""
810
 
811
- #: includes/class-freemius.php:24889, templates/connect.php:299
812
  msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:"
813
  msgstr ""
814
 
815
- #: includes/class-freemius.php:25171
816
  msgid "License key is empty."
817
  msgstr ""
818
 
@@ -824,31 +816,31 @@ msgstr ""
824
  msgid "Buy license"
825
  msgstr ""
826
 
827
- #: includes/class-fs-plugin-updater.php:331, includes/class-fs-plugin-updater.php:364
828
  msgid "There is a %s of %s available."
829
  msgstr ""
830
 
831
- #: includes/class-fs-plugin-updater.php:333, includes/class-fs-plugin-updater.php:369
832
  msgid "new Beta version"
833
  msgstr ""
834
 
835
- #: includes/class-fs-plugin-updater.php:334, includes/class-fs-plugin-updater.php:370
836
  msgid "new version"
837
  msgstr ""
838
 
839
- #: includes/class-fs-plugin-updater.php:393
840
  msgid "Important Upgrade Notice:"
841
  msgstr ""
842
 
843
- #: includes/class-fs-plugin-updater.php:1328
844
  msgid "Installing plugin: %s"
845
  msgstr ""
846
 
847
- #: includes/class-fs-plugin-updater.php:1369
848
  msgid "Unable to connect to the filesystem. Please confirm your credentials."
849
  msgstr ""
850
 
851
- #: includes/class-fs-plugin-updater.php:1551
852
  msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work."
853
  msgstr ""
854
 
@@ -895,7 +887,7 @@ msgstr ""
895
  msgid "Activate this add-on"
896
  msgstr ""
897
 
898
- #: includes/fs-plugin-info-dialog.php:789, templates/connect.php:467
899
  msgid "Activate Free Version"
900
  msgstr ""
901
 
@@ -1497,198 +1489,198 @@ msgid "PCI compliant"
1497
  msgstr ""
1498
 
1499
  #. translators: %s: name (e.g. Hey John,)
1500
- #: templates/connect.php:121
1501
  msgctxt "greeting"
1502
  msgid "Hey %s,"
1503
  msgstr ""
1504
 
1505
- #: templates/connect.php:171
1506
  msgid "Allow & Continue"
1507
  msgstr ""
1508
 
1509
- #: templates/connect.php:175
1510
  msgid "Re-send activation email"
1511
  msgstr ""
1512
 
1513
- #: templates/connect.php:179
1514
  msgid "Thanks %s!"
1515
  msgstr ""
1516
 
1517
- #: templates/connect.php:189, templates/forms/license-activation.php:46
1518
  msgid "Agree & Activate License"
1519
  msgstr ""
1520
 
1521
- #: templates/connect.php:193
1522
  msgid "Welcome to %s! To get started, please enter your license key:"
1523
  msgstr ""
1524
 
1525
- #: templates/connect.php:200
1526
  msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s."
1527
  msgstr ""
1528
 
1529
- #: templates/connect.php:201
1530
  msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s."
1531
  msgstr ""
1532
 
1533
- #: templates/connect.php:207
1534
  msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine."
1535
  msgstr ""
1536
 
1537
- #: templates/connect.php:208
1538
  msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine."
1539
  msgstr ""
1540
 
1541
- #: templates/connect.php:242
1542
  msgid "We're excited to introduce the Freemius network-level integration."
1543
  msgstr ""
1544
 
1545
- #: templates/connect.php:245
1546
  msgid "During the update process we detected %d site(s) that are still pending license activation."
1547
  msgstr ""
1548
 
1549
- #: templates/connect.php:247
1550
  msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button."
1551
  msgstr ""
1552
 
1553
- #: templates/connect.php:249
1554
  msgid "%s's paid features"
1555
  msgstr ""
1556
 
1557
- #: templates/connect.php:254
1558
  msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page."
1559
  msgstr ""
1560
 
1561
- #: templates/connect.php:256
1562
  msgid "During the update process we detected %s site(s) in the network that are still pending your attention."
1563
  msgstr ""
1564
 
1565
- #: templates/connect.php:265, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:49
1566
  msgid "License key"
1567
  msgstr ""
1568
 
1569
- #: templates/connect.php:268, templates/forms/license-activation.php:22
1570
  msgid "Can't find your license key?"
1571
  msgstr ""
1572
 
1573
- #: templates/connect.php:327, templates/connect.php:709, templates/forms/deactivation/retry-skip.php:20
1574
  msgctxt "verb"
1575
  msgid "Skip"
1576
  msgstr ""
1577
 
1578
- #: templates/connect.php:330
1579
  msgid "Delegate to Site Admins"
1580
  msgstr ""
1581
 
1582
- #: templates/connect.php:330
1583
  msgid "If you click it, this decision will be delegated to the sites administrators."
1584
  msgstr ""
1585
 
1586
- #: templates/connect.php:355
1587
  msgid "License issues?"
1588
  msgstr ""
1589
 
1590
- #: templates/connect.php:371
1591
  msgid "Your Profile Overview"
1592
  msgstr ""
1593
 
1594
- #: templates/connect.php:372
1595
  msgid "Name and email address"
1596
  msgstr ""
1597
 
1598
- #: templates/connect.php:379
1599
  msgid "So you can manage and control your license remotely from the User Dashboard."
1600
  msgstr ""
1601
 
1602
- #: templates/connect.php:380
1603
  msgid "Your Site Overview"
1604
  msgstr ""
1605
 
1606
- #: templates/connect.php:381
1607
  msgid "Site URL, WP version, PHP info"
1608
  msgstr ""
1609
 
1610
- #: templates/connect.php:388
1611
  msgid "Admin Notices"
1612
  msgstr ""
1613
 
1614
- #: templates/connect.php:389, templates/connect.php:407
1615
  msgid "Updates, announcements, marketing, no spam"
1616
  msgstr ""
1617
 
1618
- #: templates/connect.php:396
1619
  msgid "So you can reuse the license when the %s is no longer active."
1620
  msgstr ""
1621
 
1622
- #: templates/connect.php:397
1623
  msgid "Current %s Status"
1624
  msgstr ""
1625
 
1626
- #: templates/connect.php:398
1627
  msgid "Active, deactivated, or uninstalled"
1628
  msgstr ""
1629
 
1630
- #: templates/connect.php:406
1631
  msgid "Newsletter"
1632
  msgstr ""
1633
 
1634
- #: templates/connect.php:414
1635
  msgid "Plugins & Themes"
1636
  msgstr ""
1637
 
1638
- #: templates/connect.php:414
1639
  msgid "optional"
1640
  msgstr ""
1641
 
1642
- #: templates/connect.php:415
1643
  msgid "To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts."
1644
  msgstr ""
1645
 
1646
- #: templates/connect.php:416
1647
  msgid "Title, slug, version, and is active"
1648
  msgstr ""
1649
 
1650
- #: templates/connect.php:433
1651
  msgid "The %1$s will periodically send %2$s to %3$s for security & feature updates delivery, and license management."
1652
  msgstr ""
1653
 
1654
- #: templates/connect.php:435
1655
  msgid "diagnostic data"
1656
  msgstr ""
1657
 
1658
- #: templates/connect.php:436
1659
  msgid "Freemius is our licensing and software updates engine"
1660
  msgstr ""
1661
 
1662
- #: templates/connect.php:439
1663
  msgid "What permissions are being granted?"
1664
  msgstr ""
1665
 
1666
- #: templates/connect.php:466
1667
  msgid "Don't have a license key?"
1668
  msgstr ""
1669
 
1670
- #: templates/connect.php:469
1671
  msgid "Have a license key?"
1672
  msgstr ""
1673
 
1674
- #: templates/connect.php:477
1675
  msgid "Privacy Policy"
1676
  msgstr ""
1677
 
1678
- #: templates/connect.php:479
1679
  msgid "License Agreement"
1680
  msgstr ""
1681
 
1682
- #: templates/connect.php:479
1683
  msgid "Terms of Service"
1684
  msgstr ""
1685
 
1686
- #: templates/connect.php:875
1687
  msgctxt "as in the process of sending an email"
1688
  msgid "Sending email"
1689
  msgstr ""
1690
 
1691
- #: templates/connect.php:876
1692
  msgctxt "as activating plugin"
1693
  msgid "Activating"
1694
  msgstr ""
32
  msgid "Error"
33
  msgstr ""
34
 
35
+ #: includes/class-freemius.php:2540
36
  msgid "I found a better %s"
37
  msgstr ""
38
 
39
+ #: includes/class-freemius.php:2542
40
  msgid "What's the %s's name?"
41
  msgstr ""
42
 
43
+ #: includes/class-freemius.php:2548
44
  msgid "It's a temporary %s. I'm just debugging an issue."
45
  msgstr ""
46
 
47
+ #: includes/class-freemius.php:2550
48
  msgid "Deactivation"
49
  msgstr ""
50
 
51
+ #: includes/class-freemius.php:2551
52
  msgid "Theme Switch"
53
  msgstr ""
54
 
55
+ #: includes/class-freemius.php:2560, templates/forms/resend-key.php:24, templates/forms/user-change.php:29
56
  msgid "Other"
57
  msgstr ""
58
 
59
+ #: includes/class-freemius.php:2568
60
  msgid "I no longer need the %s"
61
  msgstr ""
62
 
63
+ #: includes/class-freemius.php:2575
64
  msgid "I only needed the %s for a short period"
65
  msgstr ""
66
 
67
+ #: includes/class-freemius.php:2581
68
  msgid "The %s broke my site"
69
  msgstr ""
70
 
71
+ #: includes/class-freemius.php:2588
72
  msgid "The %s suddenly stopped working"
73
  msgstr ""
74
 
75
+ #: includes/class-freemius.php:2598
76
  msgid "I can't pay for it anymore"
77
  msgstr ""
78
 
79
+ #: includes/class-freemius.php:2600
80
  msgid "What price would you feel comfortable paying?"
81
  msgstr ""
82
 
83
+ #: includes/class-freemius.php:2606
84
  msgid "I don't like to share my information with you"
85
  msgstr ""
86
 
87
+ #: includes/class-freemius.php:2627
88
  msgid "The %s didn't work"
89
  msgstr ""
90
 
91
+ #: includes/class-freemius.php:2637
92
  msgid "I couldn't understand how to make it work"
93
  msgstr ""
94
 
95
+ #: includes/class-freemius.php:2645
96
  msgid "The %s is great, but I need specific feature that you don't support"
97
  msgstr ""
98
 
99
+ #: includes/class-freemius.php:2647
100
  msgid "What feature?"
101
  msgstr ""
102
 
103
+ #: includes/class-freemius.php:2651
104
  msgid "The %s is not working"
105
  msgstr ""
106
 
107
+ #: includes/class-freemius.php:2653
108
  msgid "Kindly share what didn't work so we can fix it for future users..."
109
  msgstr ""
110
 
111
+ #: includes/class-freemius.php:2657
112
  msgid "It's not what I was looking for"
113
  msgstr ""
114
 
115
+ #: includes/class-freemius.php:2659
116
  msgid "What you've been looking for?"
117
  msgstr ""
118
 
119
+ #: includes/class-freemius.php:2663
120
  msgid "The %s didn't work as expected"
121
  msgstr ""
122
 
123
+ #: includes/class-freemius.php:2665
124
  msgid "What did you expect?"
125
  msgstr ""
126
 
127
+ #: includes/class-freemius.php:3520, templates/debug.php:20
128
  msgid "Freemius Debug"
129
  msgstr ""
130
 
131
+ #: includes/class-freemius.php:4272
132
  msgid "I don't know what is cURL or how to install it, help me!"
133
  msgstr ""
134
 
135
+ #: includes/class-freemius.php:4274
136
  msgid "We'll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update."
137
  msgstr ""
138
 
139
+ #: includes/class-freemius.php:4281
140
  msgid "Great, please install cURL and enable it in your php.ini file. In addition, search for the 'disable_functions' directive in your php.ini file and remove any disabled methods starting with 'curl_'. To make sure it was successfully activated, use 'phpinfo()'. Once activated, deactivate the %s and reactivate it back again."
141
  msgstr ""
142
 
143
+ #: includes/class-freemius.php:4386
144
  msgid "Yes - do your thing"
145
  msgstr ""
146
 
147
+ #: includes/class-freemius.php:4391
148
  msgid "No - just deactivate"
149
  msgstr ""
150
 
151
+ #: includes/class-freemius.php:4436, includes/class-freemius.php:4930, includes/class-freemius.php:6191, includes/class-freemius.php:13368, includes/class-freemius.php:14110, includes/class-freemius.php:17542, includes/class-freemius.php:17647, includes/class-freemius.php:17822, includes/class-freemius.php:20056, includes/class-freemius.php:20414, includes/class-freemius.php:20424, includes/class-freemius.php:21109, includes/class-freemius.php:22015, includes/class-freemius.php:22148, includes/class-freemius.php:22304, templates/add-ons.php:57
152
  msgctxt "exclamation"
153
  msgid "Oops"
154
  msgstr ""
155
 
156
+ #: includes/class-freemius.php:4505
157
  msgid "Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience."
158
  msgstr ""
159
 
160
+ #: includes/class-freemius.php:4927
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  msgctxt "addonX cannot run without pluginY"
162
  msgid "%s cannot run without %s."
163
  msgstr ""
164
 
165
+ #: includes/class-freemius.php:4928
166
  msgctxt "addonX cannot run..."
167
  msgid "%s cannot run without the plugin."
168
  msgstr ""
169
 
170
+ #: includes/class-freemius.php:5127, includes/class-freemius.php:5152, includes/class-freemius.php:21180
171
  msgid "Unexpected API error. Please contact the %s's author with the following error."
172
  msgstr ""
173
 
174
+ #: includes/class-freemius.php:5857
175
  msgid "Premium %s version was successfully activated."
176
  msgstr ""
177
 
178
+ #: includes/class-freemius.php:5869, includes/class-freemius.php:7774
179
  msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)."
180
  msgid "W00t"
181
  msgstr ""
182
 
183
+ #: includes/class-freemius.php:5884
184
  msgid "You have a %s license."
185
  msgstr ""
186
 
187
+ #: includes/class-freemius.php:5888, includes/class-freemius.php:16947, includes/class-freemius.php:16958, includes/class-freemius.php:20325, includes/class-freemius.php:20689, includes/class-freemius.php:20758, includes/class-freemius.php:20930
188
+ msgctxt "interjection expressing joy or exuberance"
189
+ msgid "Yee-haw"
190
+ msgstr ""
191
+
192
+ #: includes/class-freemius.php:6174
193
  msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license."
194
  msgstr ""
195
 
196
+ #: includes/class-freemius.php:6178
197
  msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin."
198
  msgstr ""
199
 
200
+ #: includes/class-freemius.php:6187, templates/add-ons.php:186, templates/account/partials/addon.php:381
201
  msgid "More information about %s"
202
  msgstr ""
203
 
204
+ #: includes/class-freemius.php:6188
205
  msgid "Purchase License"
206
  msgstr ""
207
 
208
+ #: includes/class-freemius.php:7125, templates/connect.php:171
209
  msgid "You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s."
210
  msgstr ""
211
 
212
+ #: includes/class-freemius.php:7129
213
  msgid "start the trial"
214
  msgstr ""
215
 
216
+ #: includes/class-freemius.php:7130, templates/connect.php:175
217
  msgid "complete the install"
218
  msgstr ""
219
 
220
+ #: includes/class-freemius.php:7249
221
  msgid "You are just one step away - %s"
222
  msgstr ""
223
 
224
+ #: includes/class-freemius.php:7252
225
  msgctxt "%s - plugin name. As complete \"PluginX\" activation now"
226
  msgid "Complete \"%s\" Activation Now"
227
  msgstr ""
228
 
229
+ #: includes/class-freemius.php:7334
230
  msgid "We made a few tweaks to the %s, %s"
231
  msgstr ""
232
 
233
+ #: includes/class-freemius.php:7338
234
  msgid "Opt in to make \"%s\" better!"
235
  msgstr ""
236
 
237
+ #: includes/class-freemius.php:7773
238
  msgid "The upgrade of %s was successfully completed."
239
  msgstr ""
240
 
241
+ #: includes/class-freemius.php:10255, includes/class-fs-plugin-updater.php:1087, includes/class-fs-plugin-updater.php:1282, includes/class-fs-plugin-updater.php:1289, templates/auto-installation.php:32
242
  msgid "Add-On"
243
  msgstr ""
244
 
245
+ #: includes/class-freemius.php:10257, templates/account.php:394, templates/account.php:402, templates/debug.php:358, templates/debug.php:549
246
  msgid "Plugin"
247
  msgstr ""
248
 
249
+ #: includes/class-freemius.php:10258, templates/account.php:395, templates/account.php:403, templates/debug.php:358, templates/debug.php:549, templates/forms/deactivation/form.php:71
250
  msgid "Theme"
251
  msgstr ""
252
 
253
+ #: includes/class-freemius.php:13188
254
  msgid "An unknown error has occurred while trying to toggle the license's white-label mode."
255
  msgstr ""
256
 
257
+ #: includes/class-freemius.php:13202
258
  msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s."
259
  msgstr ""
260
 
261
+ #: includes/class-freemius.php:13207
262
  msgid "User Dashboard"
263
  msgstr ""
264
 
265
+ #: includes/class-freemius.php:13208
266
  msgid "revert it now"
267
  msgstr ""
268
 
269
+ #: includes/class-freemius.php:13266
270
  msgid "An unknown error has occurred while trying to set the user's beta mode."
271
  msgstr ""
272
 
273
+ #: includes/class-freemius.php:13339
274
  msgid "Invalid new user ID or email address."
275
  msgstr ""
276
 
277
+ #: includes/class-freemius.php:13369, includes/class-freemius.php:22259
278
  msgid "Sorry, we could not complete the email update. Another user with the same email is already registered."
279
  msgstr ""
280
 
281
+ #: includes/class-freemius.php:13370, includes/class-freemius.php:22260
282
  msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button."
283
  msgstr ""
284
 
285
+ #: includes/class-freemius.php:13377, includes/class-freemius.php:22267
286
  msgid "Change Ownership"
287
  msgstr ""
288
 
289
+ #: includes/class-freemius.php:13977
290
  msgid "Invalid site details collection."
291
  msgstr ""
292
 
293
+ #: includes/class-freemius.php:14097
294
  msgid "We couldn't find your email address in the system, are you sure it's the right address?"
295
  msgstr ""
296
 
297
+ #: includes/class-freemius.php:14099
298
  msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?"
299
  msgstr ""
300
 
301
+ #: includes/class-freemius.php:14373
302
  msgid "Account is pending activation."
303
  msgstr ""
304
 
305
+ #: includes/class-freemius.php:14485, templates/forms/premium-versions-upgrade-handler.php:47
306
  msgid "Buy a license now"
307
  msgstr ""
308
 
309
+ #: includes/class-freemius.php:14497, templates/forms/premium-versions-upgrade-handler.php:46
310
  msgid "Renew your license now"
311
  msgstr ""
312
 
313
+ #: includes/class-freemius.php:14501
314
  msgid "%s to access version %s security & feature updates, and support."
315
  msgstr ""
316
 
317
+ #: includes/class-freemius.php:16929
318
  msgid "%s activation was successfully completed."
319
  msgstr ""
320
 
321
+ #: includes/class-freemius.php:16943
322
  msgid "Your account was successfully activated with the %s plan."
323
  msgstr ""
324
 
325
+ #: includes/class-freemius.php:16954, includes/class-freemius.php:20754
326
  msgid "Your trial has been successfully started."
327
  msgstr ""
328
 
329
+ #: includes/class-freemius.php:17540, includes/class-freemius.php:17645, includes/class-freemius.php:17820
330
  msgid "Couldn't activate %s."
331
  msgstr ""
332
 
333
+ #: includes/class-freemius.php:17541, includes/class-freemius.php:17646, includes/class-freemius.php:17821
334
  msgid "Please contact us with the following message:"
335
  msgstr ""
336
 
337
+ #: includes/class-freemius.php:17642, templates/forms/data-debug-mode.php:162
338
  msgid "An unknown error has occurred."
339
  msgstr ""
340
 
341
+ #: includes/class-freemius.php:18178, includes/class-freemius.php:23340
342
  msgid "Upgrade"
343
  msgstr ""
344
 
345
+ #: includes/class-freemius.php:18184
346
  msgid "Start Trial"
347
  msgstr ""
348
 
349
+ #: includes/class-freemius.php:18186
350
  msgid "Pricing"
351
  msgstr ""
352
 
353
+ #: includes/class-freemius.php:18266, includes/class-freemius.php:18268
354
  msgid "Affiliation"
355
  msgstr ""
356
 
357
+ #: includes/class-freemius.php:18296, includes/class-freemius.php:18298, templates/account.php:242, templates/debug.php:324
358
  msgid "Account"
359
  msgstr ""
360
 
361
+ #: includes/class-freemius.php:18312, includes/class-freemius.php:18314, includes/customizer/class-fs-customizer-support-section.php:60
362
  msgid "Contact Us"
363
  msgstr ""
364
 
365
+ #: includes/class-freemius.php:18325, includes/class-freemius.php:18327, includes/class-freemius.php:23354, templates/account.php:121, templates/account/partials/addon.php:44
366
  msgid "Add-Ons"
367
  msgstr ""
368
 
369
+ #: includes/class-freemius.php:18361
370
  msgctxt "ASCII arrow left icon"
371
  msgid "&#x2190;"
372
  msgstr ""
373
 
374
+ #: includes/class-freemius.php:18361
375
  msgctxt "ASCII arrow right icon"
376
  msgid "&#x27a4;"
377
  msgstr ""
378
 
379
+ #: includes/class-freemius.php:18363, templates/pricing.php:109
380
  msgctxt "noun"
381
  msgid "Pricing"
382
  msgstr ""
383
 
384
+ #: includes/class-freemius.php:18576, includes/customizer/class-fs-customizer-support-section.php:67
385
  msgid "Support Forum"
386
  msgstr ""
387
 
388
+ #: includes/class-freemius.php:19550
389
  msgid "Your email has been successfully verified - you are AWESOME!"
390
  msgstr ""
391
 
392
+ #: includes/class-freemius.php:19551
393
  msgctxt "a positive response"
394
  msgid "Right on"
395
  msgstr ""
396
 
397
+ #: includes/class-freemius.php:20057
398
  msgid "seems like the key you entered doesn't match our records."
399
  msgstr ""
400
 
401
+ #: includes/class-freemius.php:20081
402
  msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link."
403
  msgstr ""
404
 
405
+ #: includes/class-freemius.php:20316
406
  msgid "Your %s Add-on plan was successfully upgraded."
407
  msgstr ""
408
 
409
+ #: includes/class-freemius.php:20318
410
  msgid "%s Add-on was successfully purchased."
411
  msgstr ""
412
 
413
+ #: includes/class-freemius.php:20321
414
  msgid "Download the latest version"
415
  msgstr ""
416
 
417
+ #: includes/class-freemius.php:20407
418
  msgid "Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s"
419
  msgstr ""
420
 
421
+ #: includes/class-freemius.php:20413, includes/class-freemius.php:20423, includes/class-freemius.php:20889, includes/class-freemius.php:20978
422
  msgid "Error received from the server:"
423
  msgstr ""
424
 
425
+ #: includes/class-freemius.php:20423
426
  msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again."
427
  msgstr ""
428
 
429
+ #: includes/class-freemius.php:20651, includes/class-freemius.php:20894, includes/class-freemius.php:20949, includes/class-freemius.php:21056
430
  msgctxt "something somebody says when they are thinking about what you have just said."
431
  msgid "Hmm"
432
  msgstr ""
433
 
434
+ #: includes/class-freemius.php:20664
435
  msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry."
436
  msgstr ""
437
 
438
+ #: includes/class-freemius.php:20665, templates/account.php:123, templates/add-ons.php:250, templates/account/partials/addon.php:46
439
  msgctxt "trial period"
440
  msgid "Trial"
441
  msgstr ""
442
 
443
+ #: includes/class-freemius.php:20670
444
  msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s."
445
  msgstr ""
446
 
447
+ #: includes/class-freemius.php:20674, includes/class-freemius.php:20733
448
  msgid "Please contact us here"
449
  msgstr ""
450
 
451
+ #: includes/class-freemius.php:20685
452
  msgid "Your plan was successfully activated."
453
  msgstr ""
454
 
455
+ #: includes/class-freemius.php:20686
456
  msgid "Your plan was successfully upgraded."
457
  msgstr ""
458
 
459
+ #: includes/class-freemius.php:20703
460
  msgid "Your plan was successfully changed to %s."
461
  msgstr ""
462
 
463
+ #: includes/class-freemius.php:20719
464
  msgid "Your license has expired. You can still continue using the free %s forever."
465
  msgstr ""
466
 
467
+ #: includes/class-freemius.php:20721
468
  msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions."
469
  msgstr ""
470
 
471
+ #: includes/class-freemius.php:20729
472
  msgid "Your license has been cancelled. If you think it's a mistake, please contact support."
473
  msgstr ""
474
 
475
+ #: includes/class-freemius.php:20742
476
  msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support."
477
  msgstr ""
478
 
479
+ #: includes/class-freemius.php:20768
480
  msgid "Your free trial has expired. You can still continue using all our free features."
481
  msgstr ""
482
 
483
+ #: includes/class-freemius.php:20770
484
  msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions."
485
  msgstr ""
486
 
487
+ #: includes/class-freemius.php:20885
488
  msgid "It looks like the license could not be activated."
489
  msgstr ""
490
 
491
+ #: includes/class-freemius.php:20927
492
  msgid "Your license was successfully activated."
493
  msgstr ""
494
 
495
+ #: includes/class-freemius.php:20953
496
  msgid "It looks like your site currently doesn't have an active license."
497
  msgstr ""
498
 
499
+ #: includes/class-freemius.php:20977
500
  msgid "It looks like the license deactivation failed."
501
  msgstr ""
502
 
503
+ #: includes/class-freemius.php:21006
504
  msgid "Your %s license was successfully deactivated."
505
  msgstr ""
506
 
507
+ #: includes/class-freemius.php:21007
508
  msgid "Your license was successfully deactivated, you are back to the %s plan."
509
  msgstr ""
510
 
511
+ #: includes/class-freemius.php:21010
512
  msgid "O.K"
513
  msgstr ""
514
 
515
+ #: includes/class-freemius.php:21063
516
  msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes."
517
  msgstr ""
518
 
519
+ #: includes/class-freemius.php:21072
520
  msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s."
521
  msgstr ""
522
 
523
+ #: includes/class-freemius.php:21114
524
  msgid "You are already running the %s in a trial mode."
525
  msgstr ""
526
 
527
+ #: includes/class-freemius.php:21125
528
  msgid "You already utilized a trial before."
529
  msgstr ""
530
 
531
+ #: includes/class-freemius.php:21139
532
  msgid "Plan %s do not exist, therefore, can't start a trial."
533
  msgstr ""
534
 
535
+ #: includes/class-freemius.php:21150
536
  msgid "Plan %s does not support a trial period."
537
  msgstr ""
538
 
539
+ #: includes/class-freemius.php:21161
540
  msgid "None of the %s's plans supports a trial period."
541
  msgstr ""
542
 
543
+ #: includes/class-freemius.php:21211
544
  msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)"
545
  msgstr ""
546
 
547
+ #: includes/class-freemius.php:21247
548
  msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes."
549
  msgstr ""
550
 
551
+ #: includes/class-freemius.php:21266
552
  msgid "Your %s free trial was successfully cancelled."
553
  msgstr ""
554
 
555
+ #: includes/class-freemius.php:21582
556
  msgid "Version %s was released."
557
  msgstr ""
558
 
559
+ #: includes/class-freemius.php:21582
560
  msgid "Please download %s."
561
  msgstr ""
562
 
563
+ #: includes/class-freemius.php:21589
564
  msgid "the latest %s version here"
565
  msgstr ""
566
 
567
+ #: includes/class-freemius.php:21594
568
  msgid "New"
569
  msgstr ""
570
 
571
+ #: includes/class-freemius.php:21599
572
  msgid "Seems like you got the latest release."
573
  msgstr ""
574
 
575
+ #: includes/class-freemius.php:21600
576
  msgid "You are all good!"
577
  msgstr ""
578
 
579
+ #: includes/class-freemius.php:21903
580
  msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box."
581
  msgstr ""
582
 
583
+ #: includes/class-freemius.php:22043
584
  msgid "Site successfully opted in."
585
  msgstr ""
586
 
587
+ #: includes/class-freemius.php:22044, includes/class-freemius.php:23050
588
  msgid "Awesome"
589
  msgstr ""
590
 
591
+ #: includes/class-freemius.php:22060, templates/forms/optout.php:41
592
  msgid "We appreciate your help in making the %s better by letting us track some usage data."
593
  msgstr ""
594
 
595
+ #: includes/class-freemius.php:22061
596
  msgid "Thank you!"
597
  msgstr ""
598
 
599
+ #: includes/class-freemius.php:22068
600
  msgid "We will no longer be sending any usage data of %s on %s to %s."
601
  msgstr ""
602
 
603
+ #: includes/class-freemius.php:22226
604
  msgid "Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder."
605
  msgstr ""
606
 
607
+ #: includes/class-freemius.php:22232
608
  msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval."
609
  msgstr ""
610
 
611
+ #: includes/class-freemius.php:22237
612
  msgid "%s is the new owner of the account."
613
  msgstr ""
614
 
615
+ #: includes/class-freemius.php:22239
616
  msgctxt "as congratulations"
617
  msgid "Congrats"
618
  msgstr ""
619
 
620
+ #: includes/class-freemius.php:22275
621
  msgid "Your email was successfully updated. You should receive an email with confirmation instructions in few moments."
622
  msgstr ""
623
 
624
+ #: includes/class-freemius.php:22287
625
  msgid "Please provide your full name."
626
  msgstr ""
627
 
628
+ #: includes/class-freemius.php:22292
629
  msgid "Your name was successfully updated."
630
  msgstr ""
631
 
632
+ #: includes/class-freemius.php:22353
633
  msgid "You have successfully updated your %s."
634
  msgstr ""
635
 
636
+ #: includes/class-freemius.php:22412
637
  msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin."
638
  msgstr ""
639
 
640
+ #: includes/class-freemius.php:22415
641
  msgid "Click here"
642
  msgstr ""
643
 
644
+ #: includes/class-freemius.php:22513
645
  msgid "Just letting you know that the add-ons information of %s is being pulled from an external server."
646
  msgstr ""
647
 
648
+ #: includes/class-freemius.php:22514
649
  msgctxt "advance notice of something that will need attention."
650
  msgid "Heads up"
651
  msgstr ""
652
 
653
+ #: includes/class-freemius.php:23090
654
  msgctxt "exclamation"
655
  msgid "Hey"
656
  msgstr ""
657
 
658
+ #: includes/class-freemius.php:23090
659
  msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial."
660
  msgstr ""
661
 
662
+ #: includes/class-freemius.php:23098
663
  msgid "No commitment for %s days - cancel anytime!"
664
  msgstr ""
665
 
666
+ #: includes/class-freemius.php:23099
667
  msgid "No credit card required"
668
  msgstr ""
669
 
670
+ #: includes/class-freemius.php:23106, templates/forms/trial-start.php:53
671
  msgctxt "call to action"
672
  msgid "Start free trial"
673
  msgstr ""
674
 
675
+ #: includes/class-freemius.php:23183
676
  msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!"
677
  msgstr ""
678
 
679
+ #: includes/class-freemius.php:23192
680
  msgid "Learn more"
681
  msgstr ""
682
 
683
+ #: includes/class-freemius.php:23378, templates/account.php:558, templates/account.php:708, templates/connect.php:179, templates/connect.php:461, templates/forms/license-activation.php:27, templates/account/partials/addon.php:321
684
  msgid "Activate License"
685
  msgstr ""
686
 
687
+ #: includes/class-freemius.php:23379, templates/account.php:652, templates/account.php:707, templates/account/partials/addon.php:322, templates/account/partials/site.php:271
688
  msgid "Change License"
689
  msgstr ""
690
 
691
+ #: includes/class-freemius.php:23500, templates/account/partials/site.php:169
692
  msgid "Opt Out"
693
  msgstr ""
694
 
695
+ #: includes/class-freemius.php:23502, includes/class-freemius.php:23508, templates/account/partials/site.php:49, templates/account/partials/site.php:169
696
  msgid "Opt In"
697
  msgstr ""
698
 
699
+ #: includes/class-freemius.php:23738
700
  msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"
701
  msgstr ""
702
 
703
+ #: includes/class-freemius.php:23746
704
  msgid "Activate %s features"
705
  msgstr ""
706
 
707
+ #: includes/class-freemius.php:23759
708
  msgid "Please follow these steps to complete the upgrade"
709
  msgstr ""
710
 
711
+ #: includes/class-freemius.php:23763
712
  msgid "Download the latest %s version"
713
  msgstr ""
714
 
715
+ #: includes/class-freemius.php:23767
716
  msgid "Upload and activate the downloaded version"
717
  msgstr ""
718
 
719
+ #: includes/class-freemius.php:23769
720
  msgid "How to upload and activate?"
721
  msgstr ""
722
 
723
+ #: includes/class-freemius.php:23903
724
  msgid "%sClick here%s to choose the sites where you'd like to activate the license on."
725
  msgstr ""
726
 
727
+ #: includes/class-freemius.php:24072
728
  msgid "Auto installation only works for opted-in users."
729
  msgstr ""
730
 
731
+ #: includes/class-freemius.php:24082, includes/class-freemius.php:24115, includes/class-fs-plugin-updater.php:1261, includes/class-fs-plugin-updater.php:1275
732
  msgid "Invalid module ID."
733
  msgstr ""
734
 
735
+ #: includes/class-freemius.php:24091, includes/class-fs-plugin-updater.php:1297
736
  msgid "Premium version already active."
737
  msgstr ""
738
 
739
+ #: includes/class-freemius.php:24098
740
  msgid "You do not have a valid license to access the premium version."
741
  msgstr ""
742
 
743
+ #: includes/class-freemius.php:24105
744
  msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version."
745
  msgstr ""
746
 
747
+ #: includes/class-freemius.php:24123, includes/class-fs-plugin-updater.php:1296
748
  msgid "Premium add-on version already installed."
749
  msgstr ""
750
 
751
+ #: includes/class-freemius.php:24473
752
  msgid "View paid features"
753
  msgstr ""
754
 
755
+ #: includes/class-freemius.php:24795
756
  msgid "Thank you so much for using %s and its add-ons!"
757
  msgstr ""
758
 
759
+ #: includes/class-freemius.php:24796
760
  msgid "Thank you so much for using %s!"
761
  msgstr ""
762
 
763
+ #: includes/class-freemius.php:24802
764
  msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s."
765
  msgstr ""
766
 
767
+ #: includes/class-freemius.php:24806
768
  msgid "Thank you so much for using our products!"
769
  msgstr ""
770
 
771
+ #: includes/class-freemius.php:24807
772
  msgid "You've already opted-in to our usage-tracking, which helps us keep improving them."
773
  msgstr ""
774
 
775
+ #: includes/class-freemius.php:24826
776
  msgid "%s and its add-ons"
777
  msgstr ""
778
 
779
+ #: includes/class-freemius.php:24835
780
  msgid "Products"
781
  msgstr ""
782
 
783
+ #: includes/class-freemius.php:24842, templates/connect.php:275
784
  msgid "Yes"
785
  msgstr ""
786
 
787
+ #: includes/class-freemius.php:24843, templates/connect.php:276
788
  msgid "send me security & feature updates, educational content and offers."
789
  msgstr ""
790
 
791
+ #: includes/class-freemius.php:24844, templates/connect.php:281
792
  msgid "No"
793
  msgstr ""
794
 
795
+ #: includes/class-freemius.php:24846, templates/connect.php:283
796
  msgid "do %sNOT%s send me security & feature updates, educational content and offers."
797
  msgstr ""
798
 
799
+ #: includes/class-freemius.php:24856
800
  msgid "Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)"
801
  msgstr ""
802
 
803
+ #: includes/class-freemius.php:24858, templates/connect.php:290
804
  msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:"
805
  msgstr ""
806
 
807
+ #: includes/class-freemius.php:25140
808
  msgid "License key is empty."
809
  msgstr ""
810
 
816
  msgid "Buy license"
817
  msgstr ""
818
 
819
+ #: includes/class-fs-plugin-updater.php:327, includes/class-fs-plugin-updater.php:360
820
  msgid "There is a %s of %s available."
821
  msgstr ""
822
 
823
+ #: includes/class-fs-plugin-updater.php:329, includes/class-fs-plugin-updater.php:365
824
  msgid "new Beta version"
825
  msgstr ""
826
 
827
+ #: includes/class-fs-plugin-updater.php:330, includes/class-fs-plugin-updater.php:366
828
  msgid "new version"
829
  msgstr ""
830
 
831
+ #: includes/class-fs-plugin-updater.php:389
832
  msgid "Important Upgrade Notice:"
833
  msgstr ""
834
 
835
+ #: includes/class-fs-plugin-updater.php:1326
836
  msgid "Installing plugin: %s"
837
  msgstr ""
838
 
839
+ #: includes/class-fs-plugin-updater.php:1367
840
  msgid "Unable to connect to the filesystem. Please confirm your credentials."
841
  msgstr ""
842
 
843
+ #: includes/class-fs-plugin-updater.php:1549
844
  msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work."
845
  msgstr ""
846
 
887
  msgid "Activate this add-on"
888
  msgstr ""
889
 
890
+ #: includes/fs-plugin-info-dialog.php:789, templates/connect.php:458
891
  msgid "Activate Free Version"
892
  msgstr ""
893
 
1489
  msgstr ""
1490
 
1491
  #. translators: %s: name (e.g. Hey John,)
1492
+ #: templates/connect.php:112
1493
  msgctxt "greeting"
1494
  msgid "Hey %s,"
1495
  msgstr ""
1496
 
1497
+ #: templates/connect.php:162
1498
  msgid "Allow & Continue"
1499
  msgstr ""
1500
 
1501
+ #: templates/connect.php:166
1502
  msgid "Re-send activation email"
1503
  msgstr ""
1504
 
1505
+ #: templates/connect.php:170
1506
  msgid "Thanks %s!"
1507
  msgstr ""
1508
 
1509
+ #: templates/connect.php:180, templates/forms/license-activation.php:46
1510
  msgid "Agree & Activate License"
1511
  msgstr ""
1512
 
1513
+ #: templates/connect.php:184
1514
  msgid "Welcome to %s! To get started, please enter your license key:"
1515
  msgstr ""
1516
 
1517
+ #: templates/connect.php:191
1518
  msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s."
1519
  msgstr ""
1520
 
1521
+ #: templates/connect.php:192
1522
  msgid "Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s."
1523
  msgstr ""
1524
 
1525
+ #: templates/connect.php:198
1526
  msgid "Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine."
1527
  msgstr ""
1528
 
1529
+ #: templates/connect.php:199
1530
  msgid "Never miss an important update - opt in to our security & feature updates notifications, and non-sensitive diagnostic tracking with %4$s. If you skip this, that's okay! %1$s will still work just fine."
1531
  msgstr ""
1532
 
1533
+ #: templates/connect.php:233
1534
  msgid "We're excited to introduce the Freemius network-level integration."
1535
  msgstr ""
1536
 
1537
+ #: templates/connect.php:236
1538
  msgid "During the update process we detected %d site(s) that are still pending license activation."
1539
  msgstr ""
1540
 
1541
+ #: templates/connect.php:238
1542
  msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button."
1543
  msgstr ""
1544
 
1545
+ #: templates/connect.php:240
1546
  msgid "%s's paid features"
1547
  msgstr ""
1548
 
1549
+ #: templates/connect.php:245
1550
  msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page."
1551
  msgstr ""
1552
 
1553
+ #: templates/connect.php:247
1554
  msgid "During the update process we detected %s site(s) in the network that are still pending your attention."
1555
  msgstr ""
1556
 
1557
+ #: templates/connect.php:256, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:49
1558
  msgid "License key"
1559
  msgstr ""
1560
 
1561
+ #: templates/connect.php:259, templates/forms/license-activation.php:22
1562
  msgid "Can't find your license key?"
1563
  msgstr ""
1564
 
1565
+ #: templates/connect.php:318, templates/connect.php:700, templates/forms/deactivation/retry-skip.php:20
1566
  msgctxt "verb"
1567
  msgid "Skip"
1568
  msgstr ""
1569
 
1570
+ #: templates/connect.php:321
1571
  msgid "Delegate to Site Admins"
1572
  msgstr ""
1573
 
1574
+ #: templates/connect.php:321
1575
  msgid "If you click it, this decision will be delegated to the sites administrators."
1576
  msgstr ""
1577
 
1578
+ #: templates/connect.php:346
1579
  msgid "License issues?"
1580
  msgstr ""
1581
 
1582
+ #: templates/connect.php:362
1583
  msgid "Your Profile Overview"
1584
  msgstr ""
1585
 
1586
+ #: templates/connect.php:363
1587
  msgid "Name and email address"
1588
  msgstr ""
1589
 
1590
+ #: templates/connect.php:370
1591
  msgid "So you can manage and control your license remotely from the User Dashboard."
1592
  msgstr ""
1593
 
1594
+ #: templates/connect.php:371
1595
  msgid "Your Site Overview"
1596
  msgstr ""
1597
 
1598
+ #: templates/connect.php:372
1599
  msgid "Site URL, WP version, PHP info"
1600
  msgstr ""
1601
 
1602
+ #: templates/connect.php:379
1603
  msgid "Admin Notices"
1604
  msgstr ""
1605
 
1606
+ #: templates/connect.php:380, templates/connect.php:398
1607
  msgid "Updates, announcements, marketing, no spam"
1608
  msgstr ""
1609
 
1610
+ #: templates/connect.php:387
1611
  msgid "So you can reuse the license when the %s is no longer active."
1612
  msgstr ""
1613
 
1614
+ #: templates/connect.php:388
1615
  msgid "Current %s Status"
1616
  msgstr ""
1617
 
1618
+ #: templates/connect.php:389
1619
  msgid "Active, deactivated, or uninstalled"
1620
  msgstr ""
1621
 
1622
+ #: templates/connect.php:397
1623
  msgid "Newsletter"
1624
  msgstr ""
1625
 
1626
+ #: templates/connect.php:405
1627
  msgid "Plugins & Themes"
1628
  msgstr ""
1629
 
1630
+ #: templates/connect.php:405
1631
  msgid "optional"
1632
  msgstr ""
1633
 
1634
+ #: templates/connect.php:406
1635
  msgid "To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts."
1636
  msgstr ""
1637
 
1638
+ #: templates/connect.php:407
1639
  msgid "Title, slug, version, and is active"
1640
  msgstr ""
1641
 
1642
+ #: templates/connect.php:424
1643
  msgid "The %1$s will periodically send %2$s to %3$s for security & feature updates delivery, and license management."
1644
  msgstr ""
1645
 
1646
+ #: templates/connect.php:426
1647
  msgid "diagnostic data"
1648
  msgstr ""
1649
 
1650
+ #: templates/connect.php:427
1651
  msgid "Freemius is our licensing and software updates engine"
1652
  msgstr ""
1653
 
1654
+ #: templates/connect.php:430
1655
  msgid "What permissions are being granted?"
1656
  msgstr ""
1657
 
1658
+ #: templates/connect.php:457
1659
  msgid "Don't have a license key?"
1660
  msgstr ""
1661
 
1662
+ #: templates/connect.php:460
1663
  msgid "Have a license key?"
1664
  msgstr ""
1665
 
1666
+ #: templates/connect.php:468
1667
  msgid "Privacy Policy"
1668
  msgstr ""
1669
 
1670
+ #: templates/connect.php:470
1671
  msgid "License Agreement"
1672
  msgstr ""
1673
 
1674
+ #: templates/connect.php:470
1675
  msgid "Terms of Service"
1676
  msgstr ""
1677
 
1678
+ #: templates/connect.php:866
1679
  msgctxt "as in the process of sending an email"
1680
  msgid "Sending email"
1681
  msgstr ""
1682
 
1683
+ #: templates/connect.php:867
1684
  msgctxt "as activating plugin"
1685
  msgid "Activating"
1686
  msgstr ""
freemius/require.php CHANGED
@@ -6,6 +6,10 @@
6
  * @since 1.1.9
7
  */
8
 
 
 
 
 
9
  // Configuration should be loaded first.
10
  require_once dirname( __FILE__ ) . '/config.php';
11
  require_once WP_FS__DIR_INCLUDES . '/fs-core-functions.php';
@@ -46,4 +50,4 @@
46
  require_once WP_FS__DIR_INCLUDES . '/class-fs-admin-notices.php';
47
  require_once WP_FS__DIR_INCLUDES . '/class-freemius-abstract.php';
48
  require_once WP_FS__DIR_INCLUDES . '/sdk/Exceptions/Exception.php';
49
- require_once WP_FS__DIR_INCLUDES . '/class-freemius.php';
6
  * @since 1.1.9
7
  */
8
 
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
  // Configuration should be loaded first.
14
  require_once dirname( __FILE__ ) . '/config.php';
15
  require_once WP_FS__DIR_INCLUDES . '/fs-core-functions.php';
50
  require_once WP_FS__DIR_INCLUDES . '/class-fs-admin-notices.php';
51
  require_once WP_FS__DIR_INCLUDES . '/class-freemius-abstract.php';
52
  require_once WP_FS__DIR_INCLUDES . '/sdk/Exceptions/Exception.php';
53
+ require_once WP_FS__DIR_INCLUDES . '/class-freemius.php';
freemius/start.php CHANGED
@@ -15,7 +15,7 @@
15
  *
16
  * @var string
17
  */
18
- $this_sdk_version = '2.4.2';
19
 
20
  #region SDK Selection Logic --------------------------------------------------------------------
21
 
15
  *
16
  * @var string
17
  */
18
+ $this_sdk_version = '2.4.3';
19
 
20
  #region SDK Selection Logic --------------------------------------------------------------------
21
 
freemius/templates/account/partials/addon.php CHANGED
@@ -1,4 +1,9 @@
1
  <?php
 
 
 
 
 
2
  /**
3
  * @var array $VARS
4
  * @var Freemius $fs
@@ -443,4 +448,4 @@
443
  </td>
444
  <!--/ Optional Delete Action -->
445
  <?php endif ?>
446
- </tr>
1
  <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit;
5
+ }
6
+
7
  /**
8
  * @var array $VARS
9
  * @var Freemius $fs
448
  </td>
449
  <!--/ Optional Delete Action -->
450
  <?php endif ?>
451
+ </tr>
freemius/templates/account/partials/site.php CHANGED
@@ -1,352 +1,352 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 2.0.0
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * @var array $VARS
15
- * @var Freemius $fs
16
- * @var FS_Plugin_License $main_license
17
- */
18
- $fs = $VARS['freemius'];
19
- $slug = $fs->get_slug();
20
- $site = $VARS['site'];
21
- $main_license = $VARS['license'];
22
- $is_data_debug_mode = $fs->is_data_debug_mode();
23
- $is_whitelabeled = $fs->is_whitelabeled();
24
- $has_paid_plan = $fs->has_paid_plan();
25
- $is_premium = $fs->is_premium();
26
- $main_user = $fs->get_user();
27
- $blog_id = $site['blog_id'];
28
-
29
- $install = $VARS['install'];
30
- $is_registered = ! empty( $install );
31
- $license = null;
32
- $trial_plan = $fs->get_trial_plan();
33
- $free_text = fs_text_inline( 'Free', 'free', $slug );
34
-
35
- if ( $is_whitelabeled && $fs->is_delegated_connection( $blog_id ) ) {
36
- $is_whitelabeled = $fs->is_whitelabeled( true, $blog_id );
37
- }
38
- ?>
39
- <tr class="fs-site-details" data-blog-id="<?php echo $blog_id ?>"<?php if ( $is_registered ) : ?> data-install-id="<?php echo $install->id ?>"<?php endif ?>>
40
- <!-- Install ID or Opt-in option -->
41
- <td><?php if ( $is_registered ) : ?>
42
- <?php echo $install->id ?>
43
- <?php else : ?>
44
- <?php $action = 'opt_in' ?>
45
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
46
- <input type="hidden" name="fs_action" value="<?php echo $action ?>">
47
- <?php wp_nonce_field( trim( "{$action}:{$blog_id}", ':' ) ) ?>
48
- <input type="hidden" name="blog_id" value="<?php echo $blog_id ?>">
49
- <button class="fs-opt-in button button-small"><?php fs_esc_html_echo_inline( 'Opt In', 'opt-in', $slug ) ?></button>
50
- </form>
51
- <?php endif ?>
52
- </td>
53
- <!--/ Install ID or Opt-in option -->
54
-
55
- <!-- Site URL -->
56
- <td class="fs-field-url fs-main-column"><?php echo fs_strip_url_protocol( $site['url'] ) ?></td>
57
- <!--/ Site URL -->
58
-
59
- <!-- License Activation / Deactivation -->
60
- <td><?php if ( $has_paid_plan ) {
61
- $view_params = array(
62
- 'freemius' => $fs,
63
- 'slug' => $slug,
64
- 'blog_id' => $blog_id,
65
- 'class' => 'button-small',
66
- );
67
-
68
- $license = null;
69
- if ( $is_registered ) {
70
- $view_params['install_id'] = $install->id;
71
- $view_params['is_localhost'] = $install->is_localhost();
72
-
73
- $has_license = FS_Plugin_License::is_valid_id( $install->license_id );
74
- $license = $has_license ?
75
- $fs->_get_license_by_id( $install->license_id ) :
76
- null;
77
- } else {
78
- $view_params['is_localhost'] = FS_Site::is_localhost_by_address( $site['url'] );
79
- }
80
-
81
- if ( ! $is_whitelabeled ) {
82
- if ( is_object( $license ) ) {
83
- $view_params['license'] = $license;
84
-
85
- // Show license deactivation button.
86
- fs_require_template( 'account/partials/deactivate-license-button.php', $view_params );
87
- } else {
88
- if ( is_object( $main_license ) && $main_license->can_activate( $view_params['is_localhost'] ) ) {
89
- // Main license is available for activation.
90
- $available_license = $main_license;
91
- } else {
92
- // Try to find any available license for activation.
93
- $available_license = $fs->_get_available_premium_license( $view_params['is_localhost'] );
94
- }
95
-
96
- if ( is_object( $available_license ) ) {
97
- $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id );
98
-
99
- $view_params['license'] = $available_license;
100
- $view_params['class'] .= ' button-primary';
101
- $view_params['plan'] = $premium_plan;
102
-
103
- fs_require_template( 'account/partials/activate-license-button.php', $view_params );
104
- }
105
- }
106
- }
107
- } ?></td>
108
- <!--/ License Activation / Deactivation -->
109
-
110
- <!-- Plan -->
111
- <td><?php if ( $is_registered ) : ?>
112
- <?php
113
- if ( ! $has_paid_plan ) {
114
- $plan_title = $free_text;
115
- } else {
116
- if ( $install->is_trial() ) {
117
- if ( is_object( $trial_plan ) && $trial_plan->id == $install->trial_plan_id ) {
118
- $plan_title = is_string( $trial_plan->name ) ?
119
- strtoupper( $trial_plan->title ) :
120
- fs_text_inline( 'Trial', 'trial', $slug );
121
- } else {
122
- $plan_title = fs_text_inline( 'Trial', 'trial', $slug );
123
- }
124
- } else {
125
- $plan = $fs->_get_plan_by_id( $install->plan_id );
126
- $plan_title = strtoupper( is_string( $plan->title ) ?
127
- $plan->title :
128
- strtoupper( $free_text )
129
- );
130
- }
131
- }
132
- ?>
133
- <code><?php echo $plan_title ?></code>
134
- <?php endif ?></td>
135
- <!--/ Plan -->
136
-
137
- <!-- More details button -->
138
- <td><?php if ( $is_registered ) : ?>
139
- <button class="fs-show-install-details button button-small">More details <i
140
- class="dashicons dashicons-arrow-right-alt2"></i>
141
- </button><?php endif ?></td>
142
- <!--/ More details button -->
143
- </tr>
144
- <?php if ( $is_registered ) : ?>
145
- <!-- More details -->
146
- <tr class="fs-install-details" data-install-id="<?php echo $install->id ?>" style="display: none">
147
- <td colspan="5">
148
- <table class="widefat fs-key-value-table">
149
- <tbody>
150
- <?php $row_index = 0 ?>
151
- <!-- Blog ID -->
152
- <tr <?php if ( 1 == $row_index % 2 ) {
153
- echo ' class="alternate"';
154
- } ?>>
155
- <td>
156
- <nobr><?php fs_esc_html_echo_inline( 'Blog ID', 'blog-id', $slug ) ?>:</nobr>
157
- </td>
158
- <td><code><?php echo $blog_id ?></code></td>
159
- <td><?php if ( ! FS_Plugin_License::is_valid_id( $install->license_id ) ) : ?>
160
- <!-- Toggle Usage Tracking -->
161
- <?php $action = 'toggle_tracking' ?>
162
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
163
- <input type="hidden" name="fs_action" value="<?php echo $action ?>">
164
- <?php wp_nonce_field( trim( "{$action}:{$blog_id}:{$install->id}", ':' ) ) ?>
165
- <input type="hidden" name="install_id" value="<?php echo $install->id ?>">
166
- <input type="hidden" name="blog_id" value="<?php echo $blog_id ?>">
167
- <button class="fs-toggle-tracking button button-small<?php if ( $install->is_disconnected ) {
168
- echo ' button-primary';
169
- } ?>" data-is-disconnected="<?php echo $install->is_disconnected ? 'true' : 'false' ?>"><?php $install->is_disconnected ? fs_esc_html_echo_inline( 'Opt In', 'opt-in', $slug ) : fs_esc_html_echo_inline( 'Opt Out', 'opt-out', $slug ) ?></button>
170
- </form>
171
- <!--/ Toggle Usage Tracking -->
172
- <?php endif ?></td>
173
- </tr>
174
- <?php $row_index ++ ?>
175
- <!--/ Blog ID -->
176
-
177
- <?php if ( $is_registered && $install->user_id != $main_user->id ) : ?>
178
- <?php
179
- /**
180
- * @var FS_User $user
181
- */
182
- $user = Freemius::_get_user_by_id( $install->user_id ) ?>
183
- <?php if ( is_object( $user ) ) : ?>
184
- <!-- User Name -->
185
- <tr <?php if ( 1 == $row_index % 2 ) {
186
- echo ' class="alternate"';
187
- } ?>>
188
- <td>
189
- <nobr><?php fs_esc_html_echo_inline( 'Owner Name', 'owner-name', $slug ) ?>:</nobr>
190
- </td>
191
- <td colspan="2"><code><?php echo htmlspecialchars( $user->get_name() ) ?></code></td>
192
- </tr>
193
- <?php $row_index ++ ?>
194
- <!--/ User Name -->
195
-
196
- <!-- User Email -->
197
- <tr <?php if ( 1 == $row_index % 2 ) {
198
- echo ' class="alternate"';
199
- } ?>>
200
- <td>
201
- <nobr><?php fs_esc_html_echo_inline( 'Owner Email', 'owner-email', $slug ) ?>:</nobr>
202
- </td>
203
- <td colspan="2"><code><?php echo htmlspecialchars( $user->email ) ?></code></td>
204
- </tr>
205
- <?php $row_index ++ ?>
206
- <!--/ User Email -->
207
-
208
- <!-- User ID -->
209
- <tr <?php if ( 1 == $row_index % 2 ) {
210
- echo ' class="alternate"';
211
- } ?>>
212
- <td>
213
- <nobr><?php fs_esc_html_echo_inline( 'Owner ID', 'owner-id', $slug ) ?>:</nobr>
214
- </td>
215
- <td colspan="2"><code><?php echo $user->id ?></code></td>
216
- </tr>
217
- <?php $row_index ++ ?>
218
- <!--/ User ID -->
219
- <?php endif ?>
220
- <?php endif ?>
221
-
222
- <!-- Public Key -->
223
- <tr <?php if ( 1 == $row_index % 2 ) {
224
- echo ' class="alternate"';
225
- } ?>>
226
- <td>
227
- <nobr><?php fs_esc_html_echo_inline( 'Public Key', 'public-key', $slug ) ?>:</nobr>
228
- </td>
229
- <td colspan="2"><code><?php echo htmlspecialchars( $install->public_key ) ?></code></td>
230
- </tr>
231
- <?php $row_index ++ ?>
232
- <!--/ Public Key -->
233
-
234
- <!-- Secret Key -->
235
- <tr <?php if ( 1 == $row_index % 2 ) {
236
- echo ' class="alternate"';
237
- } ?>>
238
- <td>
239
- <nobr><?php fs_esc_html_echo_inline( 'Secret Key', 'secret-key', $slug ) ?>:</nobr>
240
- </td>
241
- <td>
242
- <code><?php echo FS_Plugin_License::mask_secret_key_for_html( $install->secret_key ) ?></code>
243
- <?php if ( ! $is_whitelabeled ) : ?>
244
- <input type="text" value="<?php echo htmlspecialchars( $install->secret_key ) ?>"
245
- style="display: none" readonly/></td>
246
- <?php endif ?>
247
- <?php if ( ! $is_whitelabeled ) : ?>
248
- <td><button class="button button-small fs-toggle-visibility"><?php fs_esc_html_echo_x_inline( 'Show', 'verb', 'show', $slug ) ?></button></td>
249
- <?php endif ?>
250
- </tr>
251
- <?php $row_index ++ ?>
252
- <!--/ Secret Key -->
253
-
254
- <?php if ( is_object( $license ) ) : ?>
255
- <!-- License Key -->
256
- <tr <?php if ( 1 == $row_index % 2 ) {
257
- echo ' class="alternate"';
258
- } ?>>
259
- <td>
260
- <nobr><?php fs_esc_html_echo_inline( 'License Key', 'license-key', $slug ) ?>:</nobr>
261
- </td>
262
- <td>
263
- <code><?php echo $license->get_html_escaped_masked_secret_key() ?></code>
264
- <?php if ( ! $is_whitelabeled ) : ?>
265
- <input type="text" value="<?php echo htmlspecialchars( $license->secret_key ) ?>"
266
- style="display: none" readonly/></td>
267
- <?php endif ?>
268
- <?php if ( ! $is_whitelabeled ) : ?>
269
- <td>
270
- <button class="button button-small fs-toggle-visibility"><?php fs_esc_html_echo_x_inline( 'Show', 'verb', 'show', $slug ) ?></button>
271
- <button class="button button-small activate-license-trigger <?php echo $fs->get_unique_affix() ?>"><?php fs_esc_html_echo_inline( 'Change License', 'change-license', $slug ) ?></button>
272
- </td>
273
- <?php endif ?>
274
- </tr>
275
- <?php $row_index ++ ?>
276
- <!--/ License Key -->
277
-
278
- <?php if ( ! is_object( $main_license ) || $main_license->id != $license->id ) : ?>
279
- <?php $subscription = $fs->_get_subscription( $license->id ) ?>
280
- <?php if ( ! $license->is_lifetime() && is_object( $subscription ) ) : ?>
281
- <!-- Subscription -->
282
- <tr <?php if ( 1 == $row_index % 2 ) {
283
- echo ' class="alternate"';
284
- } ?>>
285
- <td>
286
- <nobr><?php fs_esc_html_echo_inline( 'Subscription', 'subscription', $slug ) ?>:</nobr>
287
- </td>
288
- <?php
289
- $is_active_subscription = $subscription->is_active();
290
-
291
- $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug );
292
- /* translators: %s: Time period (e.g. Expires in "2 months") */
293
- $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug );
294
- ?>
295
- <td>
296
- <code><?php echo $subscription->id ?> - <?php
297
- echo ( 12 == $subscription->billing_cycle ?
298
- _fs_text_inline( 'Annual', 'annual', $slug ) :
299
- _fs_text_inline( 'Monthly', 'monthly', $slug )
300
- );
301
- ?>
302
- </code>
303
- <?php if ( ! $is_active_subscription && ! $license->is_first_payment_pending() ) : ?>
304
- <label class="fs-tag fs-warn"><?php echo esc_html( sprintf( $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ) ) ?></label>
305
- <?php elseif ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) : ?>
306
- <label class="fs-tag fs-success"><?php echo esc_html( sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ) ) ?></label>
307
- <?php endif ?>
308
- </td>
309
- <td><?php if ( $is_active_subscription ) : ?>
310
- <?php
311
- $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug );
312
- $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug );
313
- /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */
314
- $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug );
315
- $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug );
316
- $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug );
317
- $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug );
318
- $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug );
319
-
320
- $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) );
321
- $downgrade_confirmation_message = sprintf(
322
- $downgrade_x_confirm_text,
323
- ( $fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ),
324
- $plan->title,
325
- $human_readable_license_expiration
326
- );
327
-
328
- $after_downgrade_message = ! $license->is_block_features ?
329
- sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) :
330
- sprintf( $after_downgrade_blocking_text, $plan->title );
331
- ?>
332
- <?php $action = 'downgrade_account' ?>
333
- <form id="fs_downgrade" action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
334
- <input type="hidden" name="fs_action" value="<?php echo $action ?>">
335
- <?php wp_nonce_field( trim( "{$action}:{$blog_id}", ':' ) ) ?>
336
- <input type="hidden" name="blog_id" value="<?php echo $blog_id ?>">
337
- <button class="button button-small" onclick="if (confirm('<?php echo esc_attr( $downgrade_confirmation_message . ' ' . $after_downgrade_message . ' ' . $prices_increase_text ) ?>')) { this.parentNode.submit(); } else { return false; }"><?php echo $downgrade_text ?></button>
338
- </form>
339
- <?php endif ?></td>
340
- </tr>
341
- <?php $row_index ++ ?>
342
- <?php endif ?>
343
- <!--/ Subscription -->
344
- <?php endif ?>
345
- <?php endif ?>
346
-
347
- </tbody>
348
- </table>
349
- </td>
350
- </tr>
351
- <!--/ More details -->
352
  <?php endif ?>
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 2.0.0
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * @var array $VARS
15
+ * @var Freemius $fs
16
+ * @var FS_Plugin_License $main_license
17
+ */
18
+ $fs = $VARS['freemius'];
19
+ $slug = $fs->get_slug();
20
+ $site = $VARS['site'];
21
+ $main_license = $VARS['license'];
22
+ $is_data_debug_mode = $fs->is_data_debug_mode();
23
+ $is_whitelabeled = $fs->is_whitelabeled();
24
+ $has_paid_plan = $fs->has_paid_plan();
25
+ $is_premium = $fs->is_premium();
26
+ $main_user = $fs->get_user();
27
+ $blog_id = $site['blog_id'];
28
+
29
+ $install = $VARS['install'];
30
+ $is_registered = ! empty( $install );
31
+ $license = null;
32
+ $trial_plan = $fs->get_trial_plan();
33
+ $free_text = fs_text_inline( 'Free', 'free', $slug );
34
+
35
+ if ( $is_whitelabeled && $fs->is_delegated_connection( $blog_id ) ) {
36
+ $is_whitelabeled = $fs->is_whitelabeled( true, $blog_id );
37
+ }
38
+ ?>
39
+ <tr class="fs-site-details" data-blog-id="<?php echo $blog_id ?>"<?php if ( $is_registered ) : ?> data-install-id="<?php echo $install->id ?>"<?php endif ?>>
40
+ <!-- Install ID or Opt-in option -->
41
+ <td><?php if ( $is_registered ) : ?>
42
+ <?php echo $install->id ?>
43
+ <?php else : ?>
44
+ <?php $action = 'opt_in' ?>
45
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
46
+ <input type="hidden" name="fs_action" value="<?php echo $action ?>">
47
+ <?php wp_nonce_field( trim( "{$action}:{$blog_id}", ':' ) ) ?>
48
+ <input type="hidden" name="blog_id" value="<?php echo $blog_id ?>">
49
+ <button class="fs-opt-in button button-small"><?php fs_esc_html_echo_inline( 'Opt In', 'opt-in', $slug ) ?></button>
50
+ </form>
51
+ <?php endif ?>
52
+ </td>
53
+ <!--/ Install ID or Opt-in option -->
54
+
55
+ <!-- Site URL -->
56
+ <td class="fs-field-url fs-main-column"><?php echo fs_strip_url_protocol( $site['url'] ) ?></td>
57
+ <!--/ Site URL -->
58
+
59
+ <!-- License Activation / Deactivation -->
60
+ <td><?php if ( $has_paid_plan ) {
61
+ $view_params = array(
62
+ 'freemius' => $fs,
63
+ 'slug' => $slug,
64
+ 'blog_id' => $blog_id,
65
+ 'class' => 'button-small',
66
+ );
67
+
68
+ $license = null;
69
+ if ( $is_registered ) {
70
+ $view_params['install_id'] = $install->id;
71
+ $view_params['is_localhost'] = $install->is_localhost();
72
+
73
+ $has_license = FS_Plugin_License::is_valid_id( $install->license_id );
74
+ $license = $has_license ?
75
+ $fs->_get_license_by_id( $install->license_id ) :
76
+ null;
77
+ } else {
78
+ $view_params['is_localhost'] = FS_Site::is_localhost_by_address( $site['url'] );
79
+ }
80
+
81
+ if ( ! $is_whitelabeled ) {
82
+ if ( is_object( $license ) ) {
83
+ $view_params['license'] = $license;
84
+
85
+ // Show license deactivation button.
86
+ fs_require_template( 'account/partials/deactivate-license-button.php', $view_params );
87
+ } else {
88
+ if ( is_object( $main_license ) && $main_license->can_activate( $view_params['is_localhost'] ) ) {
89
+ // Main license is available for activation.
90
+ $available_license = $main_license;
91
+ } else {
92
+ // Try to find any available license for activation.
93
+ $available_license = $fs->_get_available_premium_license( $view_params['is_localhost'] );
94
+ }
95
+
96
+ if ( is_object( $available_license ) ) {
97
+ $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id );
98
+
99
+ $view_params['license'] = $available_license;
100
+ $view_params['class'] .= ' button-primary';
101
+ $view_params['plan'] = $premium_plan;
102
+
103
+ fs_require_template( 'account/partials/activate-license-button.php', $view_params );
104
+ }
105
+ }
106
+ }
107
+ } ?></td>
108
+ <!--/ License Activation / Deactivation -->
109
+
110
+ <!-- Plan -->
111
+ <td><?php if ( $is_registered ) : ?>
112
+ <?php
113
+ if ( ! $has_paid_plan ) {
114
+ $plan_title = $free_text;
115
+ } else {
116
+ if ( $install->is_trial() ) {
117
+ if ( is_object( $trial_plan ) && $trial_plan->id == $install->trial_plan_id ) {
118
+ $plan_title = is_string( $trial_plan->name ) ?
119
+ strtoupper( $trial_plan->title ) :
120
+ fs_text_inline( 'Trial', 'trial', $slug );
121
+ } else {
122
+ $plan_title = fs_text_inline( 'Trial', 'trial', $slug );
123
+ }
124
+ } else {
125
+ $plan = $fs->_get_plan_by_id( $install->plan_id );
126
+ $plan_title = strtoupper( is_string( $plan->title ) ?
127
+ $plan->title :
128
+ strtoupper( $free_text )
129
+ );
130
+ }
131
+ }
132
+ ?>
133
+ <code><?php echo $plan_title ?></code>
134
+ <?php endif ?></td>
135
+ <!--/ Plan -->
136
+
137
+ <!-- More details button -->
138
+ <td><?php if ( $is_registered ) : ?>
139
+ <button class="fs-show-install-details button button-small">More details <i
140
+ class="dashicons dashicons-arrow-right-alt2"></i>
141
+ </button><?php endif ?></td>
142
+ <!--/ More details button -->
143
+ </tr>
144
+ <?php if ( $is_registered ) : ?>
145
+ <!-- More details -->
146
+ <tr class="fs-install-details" data-install-id="<?php echo $install->id ?>" style="display: none">
147
+ <td colspan="5">
148
+ <table class="widefat fs-key-value-table">
149
+ <tbody>
150
+ <?php $row_index = 0 ?>
151
+ <!-- Blog ID -->
152
+ <tr <?php if ( 1 == $row_index % 2 ) {
153
+ echo ' class="alternate"';
154
+ } ?>>
155
+ <td>
156
+ <nobr><?php fs_esc_html_echo_inline( 'Blog ID', 'blog-id', $slug ) ?>:</nobr>
157
+ </td>
158
+ <td><code><?php echo $blog_id ?></code></td>
159
+ <td><?php if ( ! FS_Plugin_License::is_valid_id( $install->license_id ) ) : ?>
160
+ <!-- Toggle Usage Tracking -->
161
+ <?php $action = 'toggle_tracking' ?>
162
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
163
+ <input type="hidden" name="fs_action" value="<?php echo $action ?>">
164
+ <?php wp_nonce_field( trim( "{$action}:{$blog_id}:{$install->id}", ':' ) ) ?>
165
+ <input type="hidden" name="install_id" value="<?php echo $install->id ?>">
166
+ <input type="hidden" name="blog_id" value="<?php echo $blog_id ?>">
167
+ <button class="fs-toggle-tracking button button-small<?php if ( $install->is_disconnected ) {
168
+ echo ' button-primary';
169
+ } ?>" data-is-disconnected="<?php echo $install->is_disconnected ? 'true' : 'false' ?>"><?php $install->is_disconnected ? fs_esc_html_echo_inline( 'Opt In', 'opt-in', $slug ) : fs_esc_html_echo_inline( 'Opt Out', 'opt-out', $slug ) ?></button>
170
+ </form>
171
+ <!--/ Toggle Usage Tracking -->
172
+ <?php endif ?></td>
173
+ </tr>
174
+ <?php $row_index ++ ?>
175
+ <!--/ Blog ID -->
176
+
177
+ <?php if ( $is_registered && $install->user_id != $main_user->id ) : ?>
178
+ <?php
179
+ /**
180
+ * @var FS_User $user
181
+ */
182
+ $user = Freemius::_get_user_by_id( $install->user_id ) ?>
183
+ <?php if ( is_object( $user ) ) : ?>
184
+ <!-- User Name -->
185
+ <tr <?php if ( 1 == $row_index % 2 ) {
186
+ echo ' class="alternate"';
187
+ } ?>>
188
+ <td>
189
+ <nobr><?php fs_esc_html_echo_inline( 'Owner Name', 'owner-name', $slug ) ?>:</nobr>
190
+ </td>
191
+ <td colspan="2"><code><?php echo htmlspecialchars( $user->get_name() ) ?></code></td>
192
+ </tr>
193
+ <?php $row_index ++ ?>
194
+ <!--/ User Name -->
195
+
196
+ <!-- User Email -->
197
+ <tr <?php if ( 1 == $row_index % 2 ) {
198
+ echo ' class="alternate"';
199
+ } ?>>
200
+ <td>
201
+ <nobr><?php fs_esc_html_echo_inline( 'Owner Email', 'owner-email', $slug ) ?>:</nobr>
202
+ </td>
203
+ <td colspan="2"><code><?php echo htmlspecialchars( $user->email ) ?></code></td>
204
+ </tr>
205
+ <?php $row_index ++ ?>
206
+ <!--/ User Email -->
207
+
208
+ <!-- User ID -->
209
+ <tr <?php if ( 1 == $row_index % 2 ) {
210
+ echo ' class="alternate"';
211
+ } ?>>
212
+ <td>
213
+ <nobr><?php fs_esc_html_echo_inline( 'Owner ID', 'owner-id', $slug ) ?>:</nobr>
214
+ </td>
215
+ <td colspan="2"><code><?php echo $user->id ?></code></td>
216
+ </tr>
217
+ <?php $row_index ++ ?>
218
+ <!--/ User ID -->
219
+ <?php endif ?>
220
+ <?php endif ?>
221
+
222
+ <!-- Public Key -->
223
+ <tr <?php if ( 1 == $row_index % 2 ) {
224
+ echo ' class="alternate"';
225
+ } ?>>
226
+ <td>
227
+ <nobr><?php fs_esc_html_echo_inline( 'Public Key', 'public-key', $slug ) ?>:</nobr>
228
+ </td>
229
+ <td colspan="2"><code><?php echo htmlspecialchars( $install->public_key ) ?></code></td>
230
+ </tr>
231
+ <?php $row_index ++ ?>
232
+ <!--/ Public Key -->
233
+
234
+ <!-- Secret Key -->
235
+ <tr <?php if ( 1 == $row_index % 2 ) {
236
+ echo ' class="alternate"';
237
+ } ?>>
238
+ <td>
239
+ <nobr><?php fs_esc_html_echo_inline( 'Secret Key', 'secret-key', $slug ) ?>:</nobr>
240
+ </td>
241
+ <td>
242
+ <code><?php echo FS_Plugin_License::mask_secret_key_for_html( $install->secret_key ) ?></code>
243
+ <?php if ( ! $is_whitelabeled ) : ?>
244
+ <input type="text" value="<?php echo htmlspecialchars( $install->secret_key ) ?>"
245
+ style="display: none" readonly/></td>
246
+ <?php endif ?>
247
+ <?php if ( ! $is_whitelabeled ) : ?>
248
+ <td><button class="button button-small fs-toggle-visibility"><?php fs_esc_html_echo_x_inline( 'Show', 'verb', 'show', $slug ) ?></button></td>
249
+ <?php endif ?>
250
+ </tr>
251
+ <?php $row_index ++ ?>
252
+ <!--/ Secret Key -->
253
+
254
+ <?php if ( is_object( $license ) ) : ?>
255
+ <!-- License Key -->
256
+ <tr <?php if ( 1 == $row_index % 2 ) {
257
+ echo ' class="alternate"';
258
+ } ?>>
259
+ <td>
260
+ <nobr><?php fs_esc_html_echo_inline( 'License Key', 'license-key', $slug ) ?>:</nobr>
261
+ </td>
262
+ <td>
263
+ <code><?php echo $license->get_html_escaped_masked_secret_key() ?></code>
264
+ <?php if ( ! $is_whitelabeled ) : ?>
265
+ <input type="text" value="<?php echo htmlspecialchars( $license->secret_key ) ?>"
266
+ style="display: none" readonly/></td>
267
+ <?php endif ?>
268
+ <?php if ( ! $is_whitelabeled ) : ?>
269
+ <td>
270
+ <button class="button button-small fs-toggle-visibility"><?php fs_esc_html_echo_x_inline( 'Show', 'verb', 'show', $slug ) ?></button>
271
+ <button class="button button-small activate-license-trigger <?php echo $fs->get_unique_affix() ?>"><?php fs_esc_html_echo_inline( 'Change License', 'change-license', $slug ) ?></button>
272
+ </td>
273
+ <?php endif ?>
274
+ </tr>
275
+ <?php $row_index ++ ?>
276
+ <!--/ License Key -->
277
+
278
+ <?php if ( ! is_object( $main_license ) || $main_license->id != $license->id ) : ?>
279
+ <?php $subscription = $fs->_get_subscription( $license->id ) ?>
280
+ <?php if ( ! $license->is_lifetime() && is_object( $subscription ) ) : ?>
281
+ <!-- Subscription -->
282
+ <tr <?php if ( 1 == $row_index % 2 ) {
283
+ echo ' class="alternate"';
284
+ } ?>>
285
+ <td>
286
+ <nobr><?php fs_esc_html_echo_inline( 'Subscription', 'subscription', $slug ) ?>:</nobr>
287
+ </td>
288
+ <?php
289
+ $is_active_subscription = $subscription->is_active();
290
+
291
+ $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug );
292
+ /* translators: %s: Time period (e.g. Expires in "2 months") */
293
+ $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug );
294
+ ?>
295
+ <td>
296
+ <code><?php echo $subscription->id ?> - <?php
297
+ echo ( 12 == $subscription->billing_cycle ?
298
+ _fs_text_inline( 'Annual', 'annual', $slug ) :
299
+ _fs_text_inline( 'Monthly', 'monthly', $slug )
300
+ );
301
+ ?>
302
+ </code>
303
+ <?php if ( ! $is_active_subscription && ! $license->is_first_payment_pending() ) : ?>
304
+ <label class="fs-tag fs-warn"><?php echo esc_html( sprintf( $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ) ) ?></label>
305
+ <?php elseif ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) : ?>
306
+ <label class="fs-tag fs-success"><?php echo esc_html( sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ) ) ?></label>
307
+ <?php endif ?>
308
+ </td>
309
+ <td><?php if ( $is_active_subscription ) : ?>
310
+ <?php
311
+ $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug );
312
+ $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug );
313
+ /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */
314
+ $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug );
315
+ $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug );
316
+ $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug );
317
+ $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug );
318
+ $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug );
319
+
320
+ $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) );
321
+ $downgrade_confirmation_message = sprintf(
322
+ $downgrade_x_confirm_text,
323
+ ( $fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ),
324
+ $plan->title,
325
+ $human_readable_license_expiration
326
+ );
327
+
328
+ $after_downgrade_message = ! $license->is_block_features ?
329
+ sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) :
330
+ sprintf( $after_downgrade_blocking_text, $plan->title );
331
+ ?>
332
+ <?php $action = 'downgrade_account' ?>
333
+ <form id="fs_downgrade" action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
334
+ <input type="hidden" name="fs_action" value="<?php echo $action ?>">
335
+ <?php wp_nonce_field( trim( "{$action}:{$blog_id}", ':' ) ) ?>
336
+ <input type="hidden" name="blog_id" value="<?php echo $blog_id ?>">
337
+ <button class="button button-small" onclick="if (confirm('<?php echo esc_attr( $downgrade_confirmation_message . ' ' . $after_downgrade_message . ' ' . $prices_increase_text ) ?>')) { this.parentNode.submit(); } else { return false; }"><?php echo $downgrade_text ?></button>
338
+ </form>
339
+ <?php endif ?></td>
340
+ </tr>
341
+ <?php $row_index ++ ?>
342
+ <?php endif ?>
343
+ <!--/ Subscription -->
344
+ <?php endif ?>
345
+ <?php endif ?>
346
+
347
+ </tbody>
348
+ </table>
349
+ </td>
350
+ </tr>
351
+ <!--/ More details -->
352
  <?php endif ?>
freemius/templates/add-ons.php CHANGED
@@ -1,502 +1,502 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.0.3
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * @var array $VARS
15
- * @var Freemius
16
- */
17
- $fs = freemius( $VARS['id'] );
18
-
19
- $slug = $fs->get_slug();
20
-
21
- $open_addon_slug = fs_request_get( 'slug' );
22
-
23
- $open_addon = false;
24
-
25
- $is_data_debug_mode = $fs->is_data_debug_mode();
26
- $is_whitelabeled = $fs->is_whitelabeled();
27
-
28
- /**
29
- * @var FS_Plugin[]
30
- */
31
- $addons = $fs->get_addons();
32
-
33
- $has_addons = ( is_array( $addons ) && 0 < count( $addons ) );
34
-
35
- $account_addon_ids = $fs->get_updated_account_addons();
36
-
37
- $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug );
38
- $view_details_text = fs_text_inline( 'View details', 'view-details', $slug );
39
-
40
- $has_tabs = $fs->_add_tabs_before_content();
41
-
42
- $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ?
43
- get_current_blog_id() :
44
- 0;
45
- ?>
46
- <div id="fs_addons" class="wrap fs-section">
47
- <?php if ( ! $has_tabs ) : ?>
48
- <h2><?php echo esc_html( sprintf( fs_text_inline( 'Add Ons for %s', 'add-ons-for-x', $slug ), $fs->get_plugin_name() ) ) ?></h2>
49
- <?php endif ?>
50
-
51
- <?php $fs->do_action( 'addons/after_title' ) ?>
52
-
53
- <div id="poststuff">
54
- <?php if ( ! $has_addons ) : ?>
55
- <h3><?php echo esc_html( sprintf(
56
- '%s... %s',
57
- fs_text_x_inline( 'Oops', 'exclamation', 'oops', $slug ),
58
- fs_text_inline( 'We couldn\'t load the add-ons list. It\'s probably an issue on our side, please try to come back in few minutes.', 'add-ons-missing', $slug )
59
- ) ) ?></h3>
60
- <?php endif ?>
61
- <ul class="fs-cards-list">
62
- <?php if ( $has_addons ) : ?>
63
- <?php
64
- $plans_and_pricing_by_addon_id = $fs->_get_addons_plans_and_pricing_map_by_id();
65
-
66
- $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id );
67
- ?>
68
- <?php
69
- $hide_all_addons_data = false;
70
-
71
- if ( $fs->is_whitelabeled_by_flag() ) {
72
- $hide_all_addons_data = true;
73
-
74
- $addon_ids = $fs->get_updated_account_addons();
75
- $installed_addons = $fs->get_installed_addons();
76
- foreach ( $installed_addons as $fs_addon ) {
77
- $addon_ids[] = $fs_addon->get_id();
78
- }
79
-
80
- if ( ! empty( $addon_ids ) ) {
81
- $addon_ids = array_unique( $addon_ids );
82
- }
83
-
84
- foreach ( $addon_ids as $addon_id ) {
85
- $addon = $fs->get_addon( $addon_id );
86
-
87
- if ( ! is_object( $addon ) ) {
88
- continue;
89
- }
90
-
91
- $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $addon->slug );
92
-
93
- if ( ! $addon_storage->is_whitelabeled ) {
94
- $hide_all_addons_data = false;
95
- break;
96
- }
97
-
98
- if ( $is_data_debug_mode ) {
99
- $is_whitelabeled = false;
100
- }
101
- }
102
- }
103
- ?>
104
- <?php foreach ( $addons as $addon ) : ?>
105
- <?php
106
- $basename = $fs->get_addon_basename( $addon->id );
107
-
108
- $is_addon_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $basename ) );
109
-
110
- if ( ! $is_addon_installed && $hide_all_addons_data ) {
111
- continue;
112
- }
113
-
114
- $is_addon_activated = $is_addon_installed ?
115
- $fs->is_addon_activated( $addon->id ) :
116
- false;
117
-
118
- $is_plugin_active = (
119
- $is_addon_activated ||
120
- isset( $active_plugins_directories_map[ dirname( $basename ) ] )
121
- );
122
-
123
- $open_addon = ( $open_addon || ( $open_addon_slug === $addon->slug ) );
124
-
125
- $price = 0;
126
- $has_trial = false;
127
- $has_free_plan = false;
128
- $has_paid_plan = false;
129
-
130
- if ( isset( $plans_and_pricing_by_addon_id[$addon->id] ) ) {
131
- $plans = $plans_and_pricing_by_addon_id[$addon->id];
132
-
133
- if ( is_array( $plans ) && 0 < count( $plans ) ) {
134
- foreach ( $plans as $plan ) {
135
- if ( ! isset( $plan->pricing ) ||
136
- ! is_array( $plan->pricing ) ||
137
- 0 == count( $plan->pricing )
138
- ) {
139
- // No pricing means a free plan.
140
- $has_free_plan = true;
141
- continue;
142
- }
143
-
144
-
145
- $has_paid_plan = true;
146
- $has_trial = $has_trial || ( is_numeric( $plan->trial_period ) && ( $plan->trial_period > 0 ) );
147
-
148
- $min_price = 999999;
149
- foreach ( $plan->pricing as $pricing ) {
150
- $pricing = new FS_Pricing( $pricing );
151
-
152
- if ( ! $pricing->is_usd() ) {
153
- /**
154
- * Skip non-USD pricing.
155
- *
156
- * @author Leo Fajardo (@leorw)
157
- * @since 2.3.1
158
- */
159
- continue;
160
- }
161
-
162
- if ( $pricing->has_annual() ) {
163
- $min_price = min( $min_price, $pricing->annual_price );
164
- } else if ( $pricing->has_monthly() ) {
165
- $min_price = min( $min_price, 12 * $pricing->monthly_price );
166
- }
167
- }
168
-
169
- if ( $min_price < 999999 ) {
170
- $price = $min_price;
171
- }
172
-
173
- }
174
- }
175
-
176
- if ( ! $has_paid_plan && ! $has_free_plan ) {
177
- continue;
178
- }
179
- }
180
- ?>
181
- <li class="fs-card fs-addon" data-slug="<?php echo $addon->slug ?>">
182
- <?php
183
- $view_details_link = sprintf( '<a href="%s" aria-label="%s" data-title="%s"',
184
- esc_url( network_admin_url( 'plugin-install.php?fs_allow_updater_and_dialog=true' . ( ! empty( $fs_blog_id ) ? '&fs_blog_id=' . $fs_blog_id : '' ) . '&tab=plugin-information&parent_plugin_id=' . $fs->get_id() . '&plugin=' . $addon->slug .
185
- '&TB_iframe=true&width=600&height=550' ) ),
186
- esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon->title ) ),
187
- esc_attr( $addon->title )
188
- ) . ' class="thickbox%s">%s</a>';
189
-
190
- echo sprintf(
191
- $view_details_link,
192
- /**
193
- * Additional class.
194
- *
195
- * @author Leo Fajardo (@leorw)
196
- * @since 2.2.4
197
- */
198
- ' fs-overlay',
199
- /**
200
- * Set the view details link text to an empty string since it is an overlay that
201
- * doesn't really need a text and whose purpose is to open the details dialog when
202
- * the card is clicked.
203
- *
204
- * @author Leo Fajardo (@leorw)
205
- * @since 2.2.4
206
- */
207
- ''
208
- );
209
- ?>
210
- <?php
211
- if ( is_null( $addon->info ) ) {
212
- $addon->info = new stdClass();
213
- }
214
- if ( ! isset( $addon->info->card_banner_url ) ) {
215
- $addon->info->card_banner_url = '//dashboard.freemius.com/assets/img/marketing/blueprint-300x100.jpg';
216
- }
217
- if ( ! isset( $addon->info->short_description ) ) {
218
- $addon->info->short_description = 'What\'s the one thing your add-on does really, really well?';
219
- }
220
- ?>
221
- <div class="fs-inner">
222
- <ul>
223
- <li class="fs-card-banner"
224
- style="background-image: url('<?php echo $addon->info->card_banner_url ?>');"><?php
225
- if ( $is_plugin_active || $is_addon_installed ) {
226
- echo sprintf(
227
- '<span class="fs-badge fs-installed-addon-badge">%s</span>',
228
- esc_html( $is_plugin_active ?
229
- fs_text_x_inline( 'Active', 'active add-on', 'active-addon', $slug ) :
230
- fs_text_x_inline( 'Installed', 'installed add-on', 'installed-addon', $slug )
231
- )
232
- );
233
- }
234
- ?></li>
235
- <!-- <li class="fs-tag"></li> -->
236
- <li class="fs-title"><?php echo $addon->title ?></li>
237
- <li class="fs-offer">
238
- <span
239
- class="fs-price"><?php
240
- if ( $is_whitelabeled ) {
241
- echo '&nbsp;';
242
- } else {
243
- $descriptors = array();
244
-
245
- if ($has_free_plan)
246
- $descriptors[] = fs_text_inline( 'Free', 'free', $slug );
247
- if ($has_paid_plan && $price > 0)
248
- $descriptors[] = '$' . number_format( $price, 2 );
249
- if ($has_trial)
250
- $descriptors[] = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug );
251
-
252
- echo implode(' - ', $descriptors);
253
-
254
- } ?></span>
255
- </li>
256
- <li class="fs-description"><?php echo ! empty( $addon->info->short_description ) ? $addon->info->short_description : 'SHORT DESCRIPTION' ?></li>
257
- <?php
258
- $is_free_only_wp_org_compliant = ( ! $has_paid_plan && $addon->is_wp_org_compliant );
259
-
260
- $is_allowed_to_install = (
261
- $fs->is_allowed_to_install() ||
262
- $is_free_only_wp_org_compliant
263
- );
264
-
265
- $show_premium_activation_or_installation_action = true;
266
-
267
- if ( ! in_array( $addon->id, $account_addon_ids ) ) {
268
- $show_premium_activation_or_installation_action = false;
269
- } else if ( $is_addon_installed ) {
270
- /**
271
- * If any add-on's version (free or premium) is installed, check if the
272
- * premium version can be activated and show the relevant action. Otherwise,
273
- * show the relevant action for the free version.
274
- *
275
- * @author Leo Fajardo (@leorw)
276
- * @since 2.4.5
277
- */
278
- $fs_addon = $is_addon_activated ?
279
- $fs->get_addon_instance( $addon->id ) :
280
- null;
281
-
282
- $premium_plugin_basename = is_object( $fs_addon ) ?
283
- $fs_addon->premium_plugin_basename() :
284
- "{$addon->premium_slug}/{$addon->slug}.php";
285
-
286
- if (
287
- ( $is_addon_activated && $fs_addon->is_premium() ) ||
288
- file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_plugin_basename ) )
289
- ) {
290
- $basename = $premium_plugin_basename;
291
- }
292
-
293
- $show_premium_activation_or_installation_action = (
294
- ( ! $is_addon_activated || ! $fs_addon->is_premium() ) &&
295
- /**
296
- * This check is needed for cases when an active add-on doesn't have an
297
- * associated Freemius instance.
298
- *
299
- * @author Leo Fajardo (@leorw)
300
- * @since 2.4.5
301
- */
302
- ( ! $is_plugin_active )
303
- );
304
- }
305
- ?>
306
- <?php if ( ! $show_premium_activation_or_installation_action ) : ?>
307
- <li class="fs-cta"><a class="button"><?php echo esc_html( $view_details_text ) ?></a></li>
308
- <?php else : ?>
309
- <?php
310
- $latest_download_local_url = $is_free_only_wp_org_compliant ?
311
- null :
312
- $fs->_get_latest_download_local_url( $addon->id );
313
- ?>
314
-
315
- <li class="fs-cta fs-dropdown">
316
- <div class="button-group">
317
- <?php if ( $is_allowed_to_install ) : ?>
318
- <?php
319
- if ( ! $is_addon_installed ) {
320
- echo sprintf(
321
- '<a class="button button-primary" href="%s">%s</a>',
322
- wp_nonce_url( self_admin_url( 'update.php?' . ( ( $has_paid_plan || ! $addon->is_wp_org_compliant ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon->slug ), 'install-plugin_' . $addon->slug ),
323
- fs_esc_html_inline( 'Install Now', 'install-now', $slug )
324
- );
325
- } else {
326
- echo sprintf(
327
- '<a class="button button-primary edit" href="%s" title="%s" target="_parent">%s</a>',
328
- wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . $basename, 'activate-plugin_' . $basename ),
329
- fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $addon->slug ),
330
- fs_text_inline( 'Activate', 'activate', $addon->slug )
331
- );
332
- }
333
- ?>
334
- <?php else : ?>
335
- <a target="_blank" rel="noopener" class="button button-primary" href="<?php echo $latest_download_local_url ?>"><?php echo esc_html( $download_latest_text ) ?></a>
336
- <?php endif ?>
337
- <div class="button button-primary fs-dropdown-arrow-button"><span class="fs-dropdown-arrow"></span><ul class="fs-dropdown-list" style="display: none">
338
- <?php if ( $is_allowed_to_install && ! empty( $latest_download_local_url ) ) : ?>
339
- <li><a target="_blank" rel="noopener" href="<?php echo $latest_download_local_url ?>"><?php echo esc_html( $download_latest_text ) ?></a></li>
340
- <?php endif ?>
341
- <li><?php
342
- echo sprintf(
343
- $view_details_link,
344
- /**
345
- * No additional class.
346
- *
347
- * @author Leo Fajardo (@leorw)
348
- * @since 2.2.4
349
- */
350
- '',
351
- /**
352
- * Set the view details link text to a non-empty string since it is an
353
- * item in the dropdown list and the text should be visible.
354
- *
355
- * @author Leo Fajardo (@leorw)
356
- * @since 2.2.4
357
- */
358
- esc_html( $view_details_text )
359
- );
360
- ?></li>
361
- </ul></div>
362
- </div>
363
- </li>
364
- <?php endif ?>
365
- </ul>
366
- </div>
367
- </li>
368
- <?php endforeach ?>
369
- <?php endif ?>
370
- </ul>
371
- </div>
372
-
373
- <?php $fs->do_action( 'addons/after_addons' ) ?>
374
- </div>
375
- <script type="text/javascript">
376
- (function( $, undef ) {
377
- <?php if ( $open_addon ) : ?>
378
-
379
- var interval = setInterval(function () {
380
- // Open add-on information page.
381
- <?php
382
- /**
383
- * @author Vova Feldman
384
- *
385
- * This code does NOT expose an XSS vulnerability because:
386
- * 1. This page only renders for admins, so if an attacker manage to get
387
- * admin access, they can do more harm.
388
- * 2. This code won't be rendered unless $open_addon_slug matches any of
389
- * the plugin's add-ons slugs.
390
- */
391
- ?>
392
- $('.fs-card[data-slug=<?php echo $open_addon_slug ?>] a').click();
393
- if ($('#TB_iframeContent').length > 0) {
394
- clearInterval(interval);
395
- interval = null;
396
- }
397
- }, 200);
398
-
399
- <?php else : ?>
400
-
401
- $( '.fs-card.fs-addon' )
402
- .mouseover(function() {
403
- var $this = $( this );
404
-
405
- $this.find( '.fs-cta .button' ).addClass( 'button-primary' );
406
-
407
- if ( 0 === $this.find( '.fs-dropdown-arrow-button.active' ).length ) {
408
- /**
409
- * When hovering over a card, close the dropdown on any other card.
410
- *
411
- * @author Leo Fajardo (@leorw)
412
- * @since 2.2.4
413
- */
414
- toggleDropdown();
415
- }
416
- }).mouseout(function( evt ) {
417
- var $relatedTarget = $( evt.relatedTarget );
418
-
419
- if ( 0 !== $relatedTarget.parents( '.fs-addon' ).length ) {
420
- return true;
421
- }
422
-
423
- var $this = $( this );
424
-
425
- /**
426
- * Set the color of the "View details" button to "secondary".
427
- *
428
- * @author Leo Fajardo (@leorw)
429
- * @since 2.2.4
430
- */
431
- $this.find( '.fs-cta .button' ).filter(function() {
432
- /**
433
- * Keep the "primary" color of the dropdown arrow button, "Install Now" button, and
434
- * "Download Latest" button.
435
-
436
- * @author Leo Fajardo (@leorw)
437
- * @since 2.2.4
438
- */
439
- return $( this ).parent().is( ':not(.button-group)' );
440
- }).removeClass('button-primary');
441
-
442
- toggleDropdown( $this.find( '.fs-dropdown' ), false );
443
- }).find( 'a.thickbox, .button:not(.fs-dropdown-arrow-button)' ).click(function() {
444
- toggleDropdown();
445
- });
446
-
447
- <?php endif ?>
448
-
449
- var $dropdowns = $( '.fs-dropdown' );
450
- if ( 0 !== $dropdowns.length ) {
451
- $dropdowns.find( '.fs-dropdown-arrow-button' ).click(function() {
452
- var $this = $( this ),
453
- $dropdown = $this.parents( '.fs-dropdown' );
454
-
455
- toggleDropdown( $dropdown, ! $dropdown.hasClass( 'active' ) );
456
- });
457
- }
458
-
459
- /**
460
- * Returns the default state of the dropdown arrow button and hides the dropdown list.
461
- *
462
- * @author Leo Fajardo (@leorw)
463
- * @since 2.2.4
464
- *
465
- * @param {(Object|undefined)} [$dropdown]
466
- * @param {(Boolean|undefined)} [state]
467
- */
468
- function toggleDropdown( $dropdown, state ) {
469
- if ( undef === $dropdown ) {
470
- var $activeDropdown = $dropdowns.find( '.active' );
471
- if ( 0 !== $activeDropdown.length ) {
472
- $dropdown = $activeDropdown;
473
- }
474
- }
475
-
476
- if ( undef === $dropdown ) {
477
- return;
478
- }
479
-
480
- if ( undef === state ) {
481
- state = false;
482
- }
483
-
484
- $dropdown.toggleClass( 'active', state );
485
- $dropdown.find( '.fs-dropdown-list' ).toggle( state );
486
- $dropdown.find( '.fs-dropdown-arrow-button' ).toggleClass( 'active', state );
487
- }
488
- })( jQuery );
489
- </script>
490
- <?php
491
- if ( $has_tabs ) {
492
- $fs->_add_tabs_after_content();
493
- }
494
-
495
- $params = array(
496
- 'page' => 'addons',
497
- 'module_id' => $fs->get_id(),
498
- 'module_type' => $fs->get_module_type(),
499
- 'module_slug' => $slug,
500
- 'module_version' => $fs->get_plugin_version(),
501
- );
502
  fs_require_template( 'powered-by.php', $params );
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.0.3
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * @var array $VARS
15
+ * @var Freemius
16
+ */
17
+ $fs = freemius( $VARS['id'] );
18
+
19
+ $slug = $fs->get_slug();
20
+
21
+ $open_addon_slug = fs_request_get( 'slug' );
22
+
23
+ $open_addon = false;
24
+
25
+ $is_data_debug_mode = $fs->is_data_debug_mode();
26
+ $is_whitelabeled = $fs->is_whitelabeled();
27
+
28
+ /**
29
+ * @var FS_Plugin[]
30
+ */
31
+ $addons = $fs->get_addons();
32
+
33
+ $has_addons = ( is_array( $addons ) && 0 < count( $addons ) );
34
+
35
+ $account_addon_ids = $fs->get_updated_account_addons();
36
+
37
+ $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug );
38
+ $view_details_text = fs_text_inline( 'View details', 'view-details', $slug );
39
+
40
+ $has_tabs = $fs->_add_tabs_before_content();
41
+
42
+ $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ?
43
+ get_current_blog_id() :
44
+ 0;
45
+ ?>
46
+ <div id="fs_addons" class="wrap fs-section">
47
+ <?php if ( ! $has_tabs ) : ?>
48
+ <h2><?php echo esc_html( sprintf( fs_text_inline( 'Add Ons for %s', 'add-ons-for-x', $slug ), $fs->get_plugin_name() ) ) ?></h2>
49
+ <?php endif ?>
50
+
51
+ <?php $fs->do_action( 'addons/after_title' ) ?>
52
+
53
+ <div id="poststuff">
54
+ <?php if ( ! $has_addons ) : ?>
55
+ <h3><?php echo esc_html( sprintf(
56
+ '%s... %s',
57
+ fs_text_x_inline( 'Oops', 'exclamation', 'oops', $slug ),
58
+ fs_text_inline( 'We couldn\'t load the add-ons list. It\'s probably an issue on our side, please try to come back in few minutes.', 'add-ons-missing', $slug )
59
+ ) ) ?></h3>
60
+ <?php endif ?>
61
+ <ul class="fs-cards-list">
62
+ <?php if ( $has_addons ) : ?>
63
+ <?php
64
+ $plans_and_pricing_by_addon_id = $fs->_get_addons_plans_and_pricing_map_by_id();
65
+
66
+ $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id );
67
+ ?>
68
+ <?php
69
+ $hide_all_addons_data = false;
70
+
71
+ if ( $fs->is_whitelabeled_by_flag() ) {
72
+ $hide_all_addons_data = true;
73
+
74
+ $addon_ids = $fs->get_updated_account_addons();
75
+ $installed_addons = $fs->get_installed_addons();
76
+ foreach ( $installed_addons as $fs_addon ) {
77
+ $addon_ids[] = $fs_addon->get_id();
78
+ }
79
+
80
+ if ( ! empty( $addon_ids ) ) {
81
+ $addon_ids = array_unique( $addon_ids );
82
+ }
83
+
84
+ foreach ( $addon_ids as $addon_id ) {
85
+ $addon = $fs->get_addon( $addon_id );
86
+
87
+ if ( ! is_object( $addon ) ) {
88
+ continue;
89
+ }
90
+
91
+ $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $addon->slug );
92
+
93
+ if ( ! $addon_storage->is_whitelabeled ) {
94
+ $hide_all_addons_data = false;
95
+ break;
96
+ }
97
+
98
+ if ( $is_data_debug_mode ) {
99
+ $is_whitelabeled = false;
100
+ }
101
+ }
102
+ }
103
+ ?>
104
+ <?php foreach ( $addons as $addon ) : ?>
105
+ <?php
106
+ $basename = $fs->get_addon_basename( $addon->id );
107
+
108
+ $is_addon_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $basename ) );
109
+
110
+ if ( ! $is_addon_installed && $hide_all_addons_data ) {
111
+ continue;
112
+ }
113
+
114
+ $is_addon_activated = $is_addon_installed ?
115
+ $fs->is_addon_activated( $addon->id ) :
116
+ false;
117
+
118
+ $is_plugin_active = (
119
+ $is_addon_activated ||
120
+ isset( $active_plugins_directories_map[ dirname( $basename ) ] )
121
+ );
122
+
123
+ $open_addon = ( $open_addon || ( $open_addon_slug === $addon->slug ) );
124
+
125
+ $price = 0;
126
+ $has_trial = false;
127
+ $has_free_plan = false;
128
+ $has_paid_plan = false;
129
+
130
+ if ( isset( $plans_and_pricing_by_addon_id[$addon->id] ) ) {
131
+ $plans = $plans_and_pricing_by_addon_id[$addon->id];
132
+
133
+ if ( is_array( $plans ) && 0 < count( $plans ) ) {
134
+ foreach ( $plans as $plan ) {
135
+ if ( ! isset( $plan->pricing ) ||
136
+ ! is_array( $plan->pricing ) ||
137
+ 0 == count( $plan->pricing )
138
+ ) {
139
+ // No pricing means a free plan.
140
+ $has_free_plan = true;
141
+ continue;
142
+ }
143
+
144
+
145
+ $has_paid_plan = true;
146
+ $has_trial = $has_trial || ( is_numeric( $plan->trial_period ) && ( $plan->trial_period > 0 ) );
147
+
148
+ $min_price = 999999;
149
+ foreach ( $plan->pricing as $pricing ) {
150
+ $pricing = new FS_Pricing( $pricing );
151
+
152
+ if ( ! $pricing->is_usd() ) {
153
+ /**
154
+ * Skip non-USD pricing.
155
+ *
156
+ * @author Leo Fajardo (@leorw)
157
+ * @since 2.3.1
158
+ */
159
+ continue;
160
+ }
161
+
162
+ if ( $pricing->has_annual() ) {
163
+ $min_price = min( $min_price, $pricing->annual_price );
164
+ } else if ( $pricing->has_monthly() ) {
165
+ $min_price = min( $min_price, 12 * $pricing->monthly_price );
166
+ }
167
+ }
168
+
169
+ if ( $min_price < 999999 ) {
170
+ $price = $min_price;
171
+ }
172
+
173
+ }
174
+ }
175
+
176
+ if ( ! $has_paid_plan && ! $has_free_plan ) {
177
+ continue;
178
+ }
179
+ }
180
+ ?>
181
+ <li class="fs-card fs-addon" data-slug="<?php echo $addon->slug ?>">
182
+ <?php
183
+ $view_details_link = sprintf( '<a href="%s" aria-label="%s" data-title="%s"',
184
+ esc_url( network_admin_url( 'plugin-install.php?fs_allow_updater_and_dialog=true' . ( ! empty( $fs_blog_id ) ? '&fs_blog_id=' . $fs_blog_id : '' ) . '&tab=plugin-information&parent_plugin_id=' . $fs->get_id() . '&plugin=' . $addon->slug .
185
+ '&TB_iframe=true&width=600&height=550' ) ),
186
+ esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon->title ) ),
187
+ esc_attr( $addon->title )
188
+ ) . ' class="thickbox%s">%s</a>';
189
+
190
+ echo sprintf(
191
+ $view_details_link,
192
+ /**
193
+ * Additional class.
194
+ *
195
+ * @author Leo Fajardo (@leorw)
196
+ * @since 2.2.4
197
+ */
198
+ ' fs-overlay',
199
+ /**
200
+ * Set the view details link text to an empty string since it is an overlay that
201
+ * doesn't really need a text and whose purpose is to open the details dialog when
202
+ * the card is clicked.
203
+ *
204
+ * @author Leo Fajardo (@leorw)
205
+ * @since 2.2.4
206
+ */
207
+ ''
208
+ );
209
+ ?>
210
+ <?php
211
+ if ( is_null( $addon->info ) ) {
212
+ $addon->info = new stdClass();
213
+ }
214
+ if ( ! isset( $addon->info->card_banner_url ) ) {
215
+ $addon->info->card_banner_url = '//dashboard.freemius.com/assets/img/marketing/blueprint-300x100.jpg';
216
+ }
217
+ if ( ! isset( $addon->info->short_description ) ) {
218
+ $addon->info->short_description = 'What\'s the one thing your add-on does really, really well?';
219
+ }
220
+ ?>
221
+ <div class="fs-inner">
222
+ <ul>
223
+ <li class="fs-card-banner"
224
+ style="background-image: url('<?php echo $addon->info->card_banner_url ?>');"><?php
225
+ if ( $is_plugin_active || $is_addon_installed ) {
226
+ echo sprintf(
227
+ '<span class="fs-badge fs-installed-addon-badge">%s</span>',
228
+ esc_html( $is_plugin_active ?
229
+ fs_text_x_inline( 'Active', 'active add-on', 'active-addon', $slug ) :
230
+ fs_text_x_inline( 'Installed', 'installed add-on', 'installed-addon', $slug )
231
+ )
232
+ );
233
+ }
234
+ ?></li>
235
+ <!-- <li class="fs-tag"></li> -->
236
+ <li class="fs-title"><?php echo $addon->title ?></li>
237
+ <li class="fs-offer">
238
+ <span
239
+ class="fs-price"><?php
240
+ if ( $is_whitelabeled ) {
241
+ echo '&nbsp;';
242
+ } else {
243
+ $descriptors = array();
244
+
245
+ if ($has_free_plan)
246
+ $descriptors[] = fs_text_inline( 'Free', 'free', $slug );
247
+ if ($has_paid_plan && $price > 0)
248
+ $descriptors[] = '$' . number_format( $price, 2 );
249
+ if ($has_trial)
250
+ $descriptors[] = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug );
251
+
252
+ echo implode(' - ', $descriptors);
253
+
254
+ } ?></span>
255
+ </li>
256
+ <li class="fs-description"><?php echo ! empty( $addon->info->short_description ) ? $addon->info->short_description : 'SHORT DESCRIPTION' ?></li>
257
+ <?php
258
+ $is_free_only_wp_org_compliant = ( ! $has_paid_plan && $addon->is_wp_org_compliant );
259
+
260
+ $is_allowed_to_install = (
261
+ $fs->is_allowed_to_install() ||
262
+ $is_free_only_wp_org_compliant
263
+ );
264
+
265
+ $show_premium_activation_or_installation_action = true;
266
+
267
+ if ( ! in_array( $addon->id, $account_addon_ids ) ) {
268
+ $show_premium_activation_or_installation_action = false;
269
+ } else if ( $is_addon_installed ) {
270
+ /**
271
+ * If any add-on's version (free or premium) is installed, check if the
272
+ * premium version can be activated and show the relevant action. Otherwise,
273
+ * show the relevant action for the free version.
274
+ *
275
+ * @author Leo Fajardo (@leorw)
276
+ * @since 2.4.5
277
+ */
278
+ $fs_addon = $is_addon_activated ?
279
+ $fs->get_addon_instance( $addon->id ) :
280
+ null;
281
+
282
+ $premium_plugin_basename = is_object( $fs_addon ) ?
283
+ $fs_addon->premium_plugin_basename() :
284
+ "{$addon->premium_slug}/{$addon->slug}.php";
285
+
286
+ if (
287
+ ( $is_addon_activated && $fs_addon->is_premium() ) ||
288
+ file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_plugin_basename ) )
289
+ ) {
290
+ $basename = $premium_plugin_basename;
291
+ }
292
+
293
+ $show_premium_activation_or_installation_action = (
294
+ ( ! $is_addon_activated || ! $fs_addon->is_premium() ) &&
295
+ /**
296
+ * This check is needed for cases when an active add-on doesn't have an
297
+ * associated Freemius instance.
298
+ *
299
+ * @author Leo Fajardo (@leorw)
300
+ * @since 2.4.5
301
+ */
302
+ ( ! $is_plugin_active )
303
+ );
304
+ }
305
+ ?>
306
+ <?php if ( ! $show_premium_activation_or_installation_action ) : ?>
307
+ <li class="fs-cta"><a class="button"><?php echo esc_html( $view_details_text ) ?></a></li>
308
+ <?php else : ?>
309
+ <?php
310
+ $latest_download_local_url = $is_free_only_wp_org_compliant ?
311
+ null :
312
+ $fs->_get_latest_download_local_url( $addon->id );
313
+ ?>
314
+
315
+ <li class="fs-cta fs-dropdown">
316
+ <div class="button-group">
317
+ <?php if ( $is_allowed_to_install ) : ?>
318
+ <?php
319
+ if ( ! $is_addon_installed ) {
320
+ echo sprintf(
321
+ '<a class="button button-primary" href="%s">%s</a>',
322
+ wp_nonce_url( self_admin_url( 'update.php?' . ( ( $has_paid_plan || ! $addon->is_wp_org_compliant ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon->slug ), 'install-plugin_' . $addon->slug ),
323
+ fs_esc_html_inline( 'Install Now', 'install-now', $slug )
324
+ );
325
+ } else {
326
+ echo sprintf(
327
+ '<a class="button button-primary edit" href="%s" title="%s" target="_parent">%s</a>',
328
+ wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . $basename, 'activate-plugin_' . $basename ),
329
+ fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $addon->slug ),
330
+ fs_text_inline( 'Activate', 'activate', $addon->slug )
331
+ );
332
+ }
333
+ ?>
334
+ <?php else : ?>
335
+ <a target="_blank" rel="noopener" class="button button-primary" href="<?php echo $latest_download_local_url ?>"><?php echo esc_html( $download_latest_text ) ?></a>
336
+ <?php endif ?>
337
+ <div class="button button-primary fs-dropdown-arrow-button"><span class="fs-dropdown-arrow"></span><ul class="fs-dropdown-list" style="display: none">
338
+ <?php if ( $is_allowed_to_install && ! empty( $latest_download_local_url ) ) : ?>
339
+ <li><a target="_blank" rel="noopener" href="<?php echo $latest_download_local_url ?>"><?php echo esc_html( $download_latest_text ) ?></a></li>
340
+ <?php endif ?>
341
+ <li><?php
342
+ echo sprintf(
343
+ $view_details_link,
344
+ /**
345
+ * No additional class.
346
+ *
347
+ * @author Leo Fajardo (@leorw)
348
+ * @since 2.2.4
349
+ */
350
+ '',
351
+ /**
352
+ * Set the view details link text to a non-empty string since it is an
353
+ * item in the dropdown list and the text should be visible.
354
+ *
355
+ * @author Leo Fajardo (@leorw)
356
+ * @since 2.2.4
357
+ */
358
+ esc_html( $view_details_text )
359
+ );
360
+ ?></li>
361
+ </ul></div>
362
+ </div>
363
+ </li>
364
+ <?php endif ?>
365
+ </ul>
366
+ </div>
367
+ </li>
368
+ <?php endforeach ?>
369
+ <?php endif ?>
370
+ </ul>
371
+ </div>
372
+
373
+ <?php $fs->do_action( 'addons/after_addons' ) ?>
374
+ </div>
375
+ <script type="text/javascript">
376
+ (function( $, undef ) {
377
+ <?php if ( $open_addon ) : ?>
378
+
379
+ var interval = setInterval(function () {
380
+ // Open add-on information page.
381
+ <?php
382
+ /**
383
+ * @author Vova Feldman
384
+ *
385
+ * This code does NOT expose an XSS vulnerability because:
386
+ * 1. This page only renders for admins, so if an attacker manage to get
387
+ * admin access, they can do more harm.
388
+ * 2. This code won't be rendered unless $open_addon_slug matches any of
389
+ * the plugin's add-ons slugs.
390
+ */
391
+ ?>
392
+ $('.fs-card[data-slug=<?php echo $open_addon_slug ?>] a').click();
393
+ if ($('#TB_iframeContent').length > 0) {
394
+ clearInterval(interval);
395
+ interval = null;
396
+ }
397
+ }, 200);
398
+
399
+ <?php else : ?>
400
+
401
+ $( '.fs-card.fs-addon' )
402
+ .mouseover(function() {
403
+ var $this = $( this );
404
+
405
+ $this.find( '.fs-cta .button' ).addClass( 'button-primary' );
406
+
407
+ if ( 0 === $this.find( '.fs-dropdown-arrow-button.active' ).length ) {
408
+ /**
409
+ * When hovering over a card, close the dropdown on any other card.
410
+ *
411
+ * @author Leo Fajardo (@leorw)
412
+ * @since 2.2.4
413
+ */
414
+ toggleDropdown();
415
+ }
416
+ }).mouseout(function( evt ) {
417
+ var $relatedTarget = $( evt.relatedTarget );
418
+
419
+ if ( 0 !== $relatedTarget.parents( '.fs-addon' ).length ) {
420
+ return true;
421
+ }
422
+
423
+ var $this = $( this );
424
+
425
+ /**
426
+ * Set the color of the "View details" button to "secondary".
427
+ *
428
+ * @author Leo Fajardo (@leorw)
429
+ * @since 2.2.4
430
+ */
431
+ $this.find( '.fs-cta .button' ).filter(function() {
432
+ /**
433
+ * Keep the "primary" color of the dropdown arrow button, "Install Now" button, and
434
+ * "Download Latest" button.
435
+
436
+ * @author Leo Fajardo (@leorw)
437
+ * @since 2.2.4
438
+ */
439
+ return $( this ).parent().is( ':not(.button-group)' );
440
+ }).removeClass('button-primary');
441
+
442
+ toggleDropdown( $this.find( '.fs-dropdown' ), false );
443
+ }).find( 'a.thickbox, .button:not(.fs-dropdown-arrow-button)' ).click(function() {
444
+ toggleDropdown();
445
+ });
446
+
447
+ <?php endif ?>
448
+
449
+ var $dropdowns = $( '.fs-dropdown' );
450
+ if ( 0 !== $dropdowns.length ) {
451
+ $dropdowns.find( '.fs-dropdown-arrow-button' ).click(function() {
452
+ var $this = $( this ),
453
+ $dropdown = $this.parents( '.fs-dropdown' );
454
+
455
+ toggleDropdown( $dropdown, ! $dropdown.hasClass( 'active' ) );
456
+ });
457
+ }
458
+
459
+ /**
460
+ * Returns the default state of the dropdown arrow button and hides the dropdown list.
461
+ *
462
+ * @author Leo Fajardo (@leorw)
463
+ * @since 2.2.4
464
+ *
465
+ * @param {(Object|undefined)} [$dropdown]
466
+ * @param {(Boolean|undefined)} [state]
467
+ */
468
+ function toggleDropdown( $dropdown, state ) {
469
+ if ( undef === $dropdown ) {
470
+ var $activeDropdown = $dropdowns.find( '.active' );
471
+ if ( 0 !== $activeDropdown.length ) {
472
+ $dropdown = $activeDropdown;
473
+ }
474
+ }
475
+
476
+ if ( undef === $dropdown ) {
477
+ return;
478
+ }
479
+
480
+ if ( undef === state ) {
481
+ state = false;
482
+ }
483
+
484
+ $dropdown.toggleClass( 'active', state );
485
+ $dropdown.find( '.fs-dropdown-list' ).toggle( state );
486
+ $dropdown.find( '.fs-dropdown-arrow-button' ).toggleClass( 'active', state );
487
+ }
488
+ })( jQuery );
489
+ </script>
490
+ <?php
491
+ if ( $has_tabs ) {
492
+ $fs->_add_tabs_after_content();
493
+ }
494
+
495
+ $params = array(
496
+ 'page' => 'addons',
497
+ 'module_id' => $fs->get_id(),
498
+ 'module_type' => $fs->get_module_type(),
499
+ 'module_slug' => $slug,
500
+ 'module_version' => $fs->get_plugin_version(),
501
+ );
502
  fs_require_template( 'powered-by.php', $params );
freemius/templates/ajax-loader.php CHANGED
@@ -1 +1,6 @@
1
- <div class="fs-ajax-loader" style="display: none"><?php for ( $i = 1; $i <= 8; $i ++ ) : ?><div class="fs-ajax-loader-bar fs-ajax-loader-bar-<?php echo $i ?>"></div><?php endfor ?></div>
 
 
 
 
 
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+ ?>
6
+ <div class="fs-ajax-loader" style="display: none"><?php for ( $i = 1; $i <= 8; $i ++ ) : ?><div class="fs-ajax-loader-bar fs-ajax-loader-bar-<?php echo $i ?>"></div><?php endfor ?></div>
freemius/templates/debug.php CHANGED
@@ -37,6 +37,8 @@
37
 
38
  $.post( ajaxurl, {
39
  action: 'fs_toggle_debug_mode',
 
 
40
  is_on : ($(this).hasClass( 'fs-on' ) ? 1 : 0)
41
  }, function ( response ) {
42
  if ( 1 == response ) {
@@ -111,7 +113,8 @@
111
  if (optionName) {
112
  $.post(ajaxurl, {
113
  action : 'fs_get_db_option',
114
- _wpnonce : '<?php echo wp_create_nonce( 'fs_get_db_option' ) ?>',
 
115
  option_name: optionName
116
  }, function (response) {
117
  if (response.data.value)
@@ -131,7 +134,8 @@
131
  if (optionValue) {
132
  $.post(ajaxurl, {
133
  action : 'fs_set_db_option',
134
- _wpnonce : '<?php echo wp_create_nonce( 'fs_set_db_option' ) ?>',
 
135
  option_name : optionName,
136
  option_value: optionValue
137
  }, function () {
@@ -724,6 +728,8 @@
724
 
725
  $.post(ajaxurl, {
726
  action : 'fs_get_debug_log',
 
 
727
  filters: filters,
728
  offset : offset,
729
  limit : limit
37
 
38
  $.post( ajaxurl, {
39
  action: 'fs_toggle_debug_mode',
40
+ // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline.
41
+ _wpnonce : <?php echo wp_json_encode( wp_create_nonce( 'fs_toggle_debug_mode' ) ); ?>,
42
  is_on : ($(this).hasClass( 'fs-on' ) ? 1 : 0)
43
  }, function ( response ) {
44
  if ( 1 == response ) {
113
  if (optionName) {
114
  $.post(ajaxurl, {
115
  action : 'fs_get_db_option',
116
+ // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline.
117
+ _wpnonce : <?php echo wp_json_encode( wp_create_nonce( 'fs_get_db_option' ) ); ?>,
118
  option_name: optionName
119
  }, function (response) {
120
  if (response.data.value)
134
  if (optionValue) {
135
  $.post(ajaxurl, {
136
  action : 'fs_set_db_option',
137
+ // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline.
138
+ _wpnonce : <?php echo wp_json_encode( wp_create_nonce( 'fs_set_db_option' ) ); ?>,
139
  option_name : optionName,
140
  option_value: optionValue
141
  }, function () {
728
 
729
  $.post(ajaxurl, {
730
  action : 'fs_get_debug_log',
731
+ // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline.
732
+ _wpnonce : <?php echo wp_json_encode( wp_create_nonce( 'fs_get_debug_log' ) ); ?>,
733
  filters: filters,
734
  offset : offset,
735
  limit : limit
freemius/templates/firewall-issues-js.php CHANGED
@@ -22,10 +22,12 @@
22
  notice = $( this ).parents( '.fs-notice' ),
23
  ajaxActionSuffix = notice.attr( 'data-manager-id' ).replace( ':', '-' );
24
 
25
- var data = {
26
- action : 'fs_resolve_firewall_issues_' + ajaxActionSuffix,
27
- error_type: error_type
28
- };
 
 
29
 
30
  if ( 'squid' === error_type ) {
31
  data.hosting_company = prompt( 'What is the name or URL of your hosting company?' );
@@ -39,7 +41,9 @@
39
  }
40
 
41
  if ( 'retry_ping' === error_type ) {
42
- data.action = 'fs_retry_connectivity_test_' + ajaxActionSuffix;
 
 
43
  }
44
 
45
  $( this ).css({'cursor': 'wait'});
@@ -56,4 +60,4 @@
56
  });
57
  });
58
  });
59
- </script>
22
  notice = $( this ).parents( '.fs-notice' ),
23
  ajaxActionSuffix = notice.attr( 'data-manager-id' ).replace( ':', '-' );
24
 
25
+ var data = {
26
+ action : 'fs_resolve_firewall_issues_' + ajaxActionSuffix,
27
+ // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline.
28
+ _wpnonce : <?php echo wp_json_encode( wp_create_nonce( 'fs_resolve_firewall_issues' ) ); ?>,
29
+ error_type: error_type
30
+ };
31
 
32
  if ( 'squid' === error_type ) {
33
  data.hosting_company = prompt( 'What is the name or URL of your hosting company?' );
41
  }
42
 
43
  if ( 'retry_ping' === error_type ) {
44
+ data.action = 'fs_retry_connectivity_test_' + ajaxActionSuffix;
45
+ // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline.
46
+ data._wpnonce = <?php echo wp_json_encode( wp_create_nonce( 'fs_retry_connectivity_test' ) ); ?>;
47
  }
48
 
49
  $( this ).css({'cursor': 'wait'});
60
  });
61
  });
62
  });
63
+ </script>
freemius/templates/forms/data-debug-mode.php CHANGED
@@ -1,213 +1,213 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 2.3.1
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * @var array $VARS
15
- *
16
- * @var Freemius $fs
17
- */
18
- $fs = freemius( $VARS['id'] );
19
- $slug = $fs->get_slug();
20
- $unique_affix = $fs->get_unique_affix();
21
- $last_license_user_id = $fs->get_last_license_user_id();
22
- $has_last_license_user_id = FS_User::is_valid_id( $last_license_user_id );
23
-
24
- $message_above_input_field = ( ! $has_last_license_user_id ) ?
25
- fs_text_inline( 'Please enter the license key to enable the debug mode:', 'submit-developer-license-key-message', $slug ) :
26
- sprintf(
27
- fs_text_inline( 'To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:', 'submit-addon-developer-key-message', $slug ),
28
- $last_license_user_id
29
- );
30
-
31
- $processing_text = ( fs_esc_js_inline( 'Processing', 'processing', $slug ) . '...' );
32
- $submit_button_text = fs_text_inline( 'Submit', 'submit', $slug );
33
- $debug_license_link_text = fs_esc_html_inline( 'Start Debug', 'start-debug-license', $slug );
34
- $license_or_user_key_text = ( ! $has_last_license_user_id ) ?
35
- fs_text_inline( 'License key', 'license-key' , $slug ) :
36
- fs_text_inline( 'User key', 'user-key' , $slug );
37
- $input_html = "<input class='fs-license-or-user-key' type='password' placeholder='{$license_or_user_key_text}' tabindex='1' />";
38
-
39
- $modal_content_html = <<< HTML
40
- <div class="notice notice-error inline license-or-user-key-submission-message"><p></p></div>
41
- <p>{$message_above_input_field}</p>
42
- {$input_html}
43
- HTML;
44
-
45
- fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
46
- ?>
47
- <script type="text/javascript">
48
- ( function( $ ) {
49
- $( document ).ready( function() {
50
- var modalContentHtml = <?php echo json_encode( $modal_content_html ) ?>,
51
- modalHtml =
52
- '<div class="fs-modal fs-modal-developer-license-debug-mode fs-modal-developer-license-debug-mode-<?php echo $unique_affix ?>">'
53
- + ' <div class="fs-modal-dialog">'
54
- + ' <div class="fs-modal-body">'
55
- + ' <div class="fs-modal-panel active">' + modalContentHtml + '</div>'
56
- + ' </div>'
57
- + ' <div class="fs-modal-footer">'
58
- + ' <button class="button button-secondary button-close" tabindex="4"><?php fs_esc_js_echo_inline( 'Cancel', 'cancel', $slug ) ?></button>'
59
- + ' <button class="button button-primary button-submit-license-or-user-key" tabindex="3"><?php echo esc_js( $submit_button_text ) ?></button>'
60
- + ' </div>'
61
- + ' </div>'
62
- + '</div>',
63
- $modal = $( modalHtml ),
64
- $debugLicenseLink = $( '.debug-license-trigger' ),
65
- $submitKeyButton = $modal.find( '.button-submit-license-or-user-key' ),
66
- $licenseOrUserKeyInput = $modal.find( 'input.fs-license-or-user-key' ),
67
- $licenseOrUserKeySubmissionMessage = $modal.find( '.license-or-user-key-submission-message' ),
68
- isDebugMode = <?php echo $fs->is_data_debug_mode() ? 'true' : 'false' ?>;
69
-
70
- $modal.appendTo( $( 'body' ) );
71
-
72
- function registerEventHandlers() {
73
- $debugLicenseLink.click(function (evt) {
74
- evt.preventDefault();
75
-
76
- if ( isDebugMode ) {
77
- setDeveloperLicenseDebugMode();
78
- return true;
79
- }
80
-
81
- showModal( evt );
82
- });
83
-
84
- $modal.on( 'input propertychange', 'input.fs-license-or-user-key', function () {
85
- var licenseOrUserKey = $( this ).val().trim();
86
-
87
- /**
88
- * If license or user key is not empty, enable the submission button.
89
- */
90
- if ( licenseOrUserKey.length > 0 ) {
91
- enableSubmitButton();
92
- }
93
- });
94
-
95
- $modal.on( 'blur', 'input.fs-license-or-user-key', function () {
96
- var licenseOrUserKey = $( this ).val().trim();
97
-
98
- /**
99
- * If license or user key is empty, disable the submission button.
100
- */
101
- if ( 0 === licenseOrUserKey.length ) {
102
- disableSubmitButton();
103
- }
104
- });
105
-
106
- $modal.on( 'click', '.button-submit-license-or-user-key', function ( evt ) {
107
- evt.preventDefault();
108
-
109
- if ( $( this ).hasClass( 'disabled' ) ) {
110
- return;
111
- }
112
-
113
- var licenseOrUserKey = $licenseOrUserKeyInput.val().trim();
114
-
115
- disableSubmitButton();
116
-
117
- if ( 0 === licenseOrUserKey.length ) {
118
- return;
119
- }
120
-
121
- setDeveloperLicenseDebugMode( licenseOrUserKey );
122
- });
123
-
124
- // If the user has clicked outside the window, close the modal.
125
- $modal.on( 'click', '.fs-close, .button-secondary', function () {
126
- closeModal();
127
- return false;
128
- } );
129
- }
130
-
131
- registerEventHandlers();
132
-
133
- function setDeveloperLicenseDebugMode( licenseOrUserKey ) {
134
- var data = {
135
- action : '<?php echo $fs->get_ajax_action( 'set_data_debug_mode' ) ?>',
136
- security : '<?php echo $fs->get_ajax_security( 'set_data_debug_mode' ) ?>',
137
- license_or_user_key: licenseOrUserKey,
138
- is_debug_mode : isDebugMode,
139
- module_id : '<?php echo $fs->get_id() ?>'
140
- };
141
-
142
- $.ajax( {
143
- url : ajaxurl,
144
- method : 'POST',
145
- data : data,
146
- beforeSend: function () {
147
- $debugLicenseLink.find('span').text( '<?php echo $processing_text ?>' );
148
- $submitKeyButton.text( '<?php echo $processing_text ?>' );
149
- },
150
- success : function ( result ) {
151
- if ( result.success ) {
152
- closeModal();
153
-
154
- // Reload the "Account" page so that the pricing/upgrade link will be properly hidden/shown.
155
- window.location.reload();
156
- } else {
157
- showError( result.error.message ? result.error.message : result.error );
158
- resetButtons();
159
- }
160
- },
161
- error : function () {
162
- showError( <?php echo json_encode( fs_text_inline( 'An unknown error has occurred.', 'unknown-error', $slug ) ) ?> );
163
- resetButtons();
164
- }
165
- });
166
- }
167
-
168
- function showModal( evt ) {
169
- resetModal();
170
-
171
- // Display the dialog box.
172
- $modal.addClass( 'active' );
173
- $( 'body' ).addClass( 'has-fs-modal' );
174
-
175
- $licenseOrUserKeyInput.val( '' );
176
- $licenseOrUserKeyInput.focus();
177
- }
178
-
179
- function closeModal() {
180
- $modal.removeClass( 'active' );
181
- $( 'body' ).removeClass( 'has-fs-modal' );
182
- }
183
-
184
- function resetButtons() {
185
- enableSubmitButton();
186
- $submitKeyButton.text( <?php echo json_encode( $submit_button_text ) ?> );
187
- $debugLicenseLink.find('span').text( <?php echo json_encode( $debug_license_link_text ) ?> );
188
- }
189
-
190
- function resetModal() {
191
- hideError();
192
- resetButtons();
193
- }
194
-
195
- function enableSubmitButton() {
196
- $submitKeyButton.removeClass( 'disabled' );
197
- }
198
-
199
- function disableSubmitButton() {
200
- $submitKeyButton.addClass( 'disabled' );
201
- }
202
-
203
- function hideError() {
204
- $licenseOrUserKeySubmissionMessage.hide();
205
- }
206
-
207
- function showError( msg ) {
208
- $licenseOrUserKeySubmissionMessage.find( ' > p' ).html( msg );
209
- $licenseOrUserKeySubmissionMessage.show();
210
- }
211
- } );
212
- } )( jQuery );
213
  </script>
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 2.3.1
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * @var array $VARS
15
+ *
16
+ * @var Freemius $fs
17
+ */
18
+ $fs = freemius( $VARS['id'] );
19
+ $slug = $fs->get_slug();
20
+ $unique_affix = $fs->get_unique_affix();
21
+ $last_license_user_id = $fs->get_last_license_user_id();
22
+ $has_last_license_user_id = FS_User::is_valid_id( $last_license_user_id );
23
+
24
+ $message_above_input_field = ( ! $has_last_license_user_id ) ?
25
+ fs_text_inline( 'Please enter the license key to enable the debug mode:', 'submit-developer-license-key-message', $slug ) :
26
+ sprintf(
27
+ fs_text_inline( 'To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:', 'submit-addon-developer-key-message', $slug ),
28
+ $last_license_user_id
29
+ );
30
+
31
+ $processing_text = ( fs_esc_js_inline( 'Processing', 'processing', $slug ) . '...' );
32
+ $submit_button_text = fs_text_inline( 'Submit', 'submit', $slug );
33
+ $debug_license_link_text = fs_esc_html_inline( 'Start Debug', 'start-debug-license', $slug );
34
+ $license_or_user_key_text = ( ! $has_last_license_user_id ) ?
35
+ fs_text_inline( 'License key', 'license-key' , $slug ) :
36
+ fs_text_inline( 'User key', 'user-key' , $slug );
37
+ $input_html = "<input class='fs-license-or-user-key' type='password' placeholder='{$license_or_user_key_text}' tabindex='1' />";
38
+
39
+ $modal_content_html = <<< HTML
40
+ <div class="notice notice-error inline license-or-user-key-submission-message"><p></p></div>
41
+ <p>{$message_above_input_field}</p>
42
+ {$input_html}
43
+ HTML;
44
+
45
+ fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
46
+ ?>
47
+ <script type="text/javascript">
48
+ ( function( $ ) {
49
+ $( document ).ready( function() {
50
+ var modalContentHtml = <?php echo json_encode( $modal_content_html ) ?>,
51
+ modalHtml =
52
+ '<div class="fs-modal fs-modal-developer-license-debug-mode fs-modal-developer-license-debug-mode-<?php echo $unique_affix ?>">'
53
+ + ' <div class="fs-modal-dialog">'
54
+ + ' <div class="fs-modal-body">'
55
+ + ' <div class="fs-modal-panel active">' + modalContentHtml + '</div>'
56
+ + ' </div>'
57
+ + ' <div class="fs-modal-footer">'
58
+ + ' <button class="button button-secondary button-close" tabindex="4"><?php fs_esc_js_echo_inline( 'Cancel', 'cancel', $slug ) ?></button>'
59
+ + ' <button class="button button-primary button-submit-license-or-user-key" tabindex="3"><?php echo esc_js( $submit_button_text ) ?></button>'
60
+ + ' </div>'
61
+ + ' </div>'
62
+ + '</div>',
63
+ $modal = $( modalHtml ),
64
+ $debugLicenseLink = $( '.debug-license-trigger' ),
65
+ $submitKeyButton = $modal.find( '.button-submit-license-or-user-key' ),
66
+ $licenseOrUserKeyInput = $modal.find( 'input.fs-license-or-user-key' ),
67
+ $licenseOrUserKeySubmissionMessage = $modal.find( '.license-or-user-key-submission-message' ),
68
+ isDebugMode = <?php echo $fs->is_data_debug_mode() ? 'true' : 'false' ?>;
69
+
70
+ $modal.appendTo( $( 'body' ) );
71
+
72
+ function registerEventHandlers() {
73
+ $debugLicenseLink.click(function (evt) {
74
+ evt.preventDefault();
75
+
76
+ if ( isDebugMode ) {
77
+ setDeveloperLicenseDebugMode();
78
+ return true;
79
+ }
80
+
81
+ showModal( evt );
82
+ });
83
+
84
+ $modal.on( 'input propertychange', 'input.fs-license-or-user-key', function () {
85
+ var licenseOrUserKey = $( this ).val().trim();
86
+
87
+ /**
88
+ * If license or user key is not empty, enable the submission button.
89
+ */
90
+ if ( licenseOrUserKey.length > 0 ) {
91
+ enableSubmitButton();
92
+ }
93
+ });
94
+
95
+ $modal.on( 'blur', 'input.fs-license-or-user-key', function () {
96
+ var licenseOrUserKey = $( this ).val().trim();
97
+
98
+ /**
99
+ * If license or user key is empty, disable the submission button.
100
+ */
101
+ if ( 0 === licenseOrUserKey.length ) {
102
+ disableSubmitButton();
103
+ }
104
+ });
105
+
106
+ $modal.on( 'click', '.button-submit-license-or-user-key', function ( evt ) {
107
+ evt.preventDefault();
108
+
109
+ if ( $( this ).hasClass( 'disabled' ) ) {
110
+ return;
111
+ }
112
+
113
+ var licenseOrUserKey = $licenseOrUserKeyInput.val().trim();
114
+
115
+ disableSubmitButton();
116
+
117
+ if ( 0 === licenseOrUserKey.length ) {
118
+ return;
119
+ }
120
+
121
+ setDeveloperLicenseDebugMode( licenseOrUserKey );
122
+ });
123
+
124
+ // If the user has clicked outside the window, close the modal.
125
+ $modal.on( 'click', '.fs-close, .button-secondary', function () {
126
+ closeModal();
127
+ return false;
128
+ } );
129
+ }
130
+
131
+ registerEventHandlers();
132
+
133
+ function setDeveloperLicenseDebugMode( licenseOrUserKey ) {
134
+ var data = {
135
+ action : '<?php echo $fs->get_ajax_action( 'set_data_debug_mode' ) ?>',
136
+ security : '<?php echo $fs->get_ajax_security( 'set_data_debug_mode' ) ?>',
137
+ license_or_user_key: licenseOrUserKey,
138
+ is_debug_mode : isDebugMode,
139
+ module_id : '<?php echo $fs->get_id() ?>'
140
+ };
141
+
142
+ $.ajax( {
143
+ url : ajaxurl,
144
+ method : 'POST',
145
+ data : data,
146
+ beforeSend: function () {
147
+ $debugLicenseLink.find('span').text( '<?php echo $processing_text ?>' );
148
+ $submitKeyButton.text( '<?php echo $processing_text ?>' );
149
+ },
150
+ success : function ( result ) {
151
+ if ( result.success ) {
152
+ closeModal();
153
+
154
+ // Reload the "Account" page so that the pricing/upgrade link will be properly hidden/shown.
155
+ window.location.reload();
156
+ } else {
157
+ showError( result.error.message ? result.error.message : result.error );
158
+ resetButtons();
159
+ }
160
+ },
161
+ error : function () {
162
+ showError( <?php echo json_encode( fs_text_inline( 'An unknown error has occurred.', 'unknown-error', $slug ) ) ?> );
163
+ resetButtons();
164
+ }
165
+ });
166
+ }
167
+
168
+ function showModal( evt ) {
169
+ resetModal();
170
+
171
+ // Display the dialog box.
172
+ $modal.addClass( 'active' );
173
+ $( 'body' ).addClass( 'has-fs-modal' );
174
+
175
+ $licenseOrUserKeyInput.val( '' );
176
+ $licenseOrUserKeyInput.focus();
177
+ }
178
+
179
+ function closeModal() {
180
+ $modal.removeClass( 'active' );
181
+ $( 'body' ).removeClass( 'has-fs-modal' );
182
+ }
183
+
184
+ function resetButtons() {
185
+ enableSubmitButton();
186
+ $submitKeyButton.text( <?php echo json_encode( $submit_button_text ) ?> );
187
+ $debugLicenseLink.find('span').text( <?php echo json_encode( $debug_license_link_text ) ?> );
188
+ }
189
+
190
+ function resetModal() {
191
+ hideError();
192
+ resetButtons();
193
+ }
194
+
195
+ function enableSubmitButton() {
196
+ $submitKeyButton.removeClass( 'disabled' );
197
+ }
198
+
199
+ function disableSubmitButton() {
200
+ $submitKeyButton.addClass( 'disabled' );
201
+ }
202
+
203
+ function hideError() {
204
+ $licenseOrUserKeySubmissionMessage.hide();
205
+ }
206
+
207
+ function showError( msg ) {
208
+ $licenseOrUserKeySubmissionMessage.find( ' > p' ).html( msg );
209
+ $licenseOrUserKeySubmissionMessage.show();
210
+ }
211
+ } );
212
+ } )( jQuery );
213
  </script>
freemius/templates/forms/optout.php CHANGED
@@ -1,336 +1,336 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.2.1.5
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * @var array $VARS
15
- * @var Freemius $fs
16
- */
17
- $fs = freemius( $VARS['id'] );
18
- $slug = $fs->get_slug();
19
-
20
- $action = $fs->is_tracking_allowed() ?
21
- 'stop_tracking' :
22
- 'allow_tracking';
23
-
24
- $reconnect_url = $fs->get_activation_url( array(
25
- 'nonce' => wp_create_nonce( $fs->get_unique_affix() . '_reconnect' ),
26
- 'fs_action' => ( $fs->get_unique_affix() . '_reconnect' ),
27
- ) );
28
-
29
- $plugin_title = "<strong>{$fs->get_plugin()->title}</strong>";
30
- $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug );
31
- $opt_in_text = fs_text_x_inline( 'Opt In', 'verb', 'opt-in', $slug );
32
-
33
- if ( $fs->is_premium() ) {
34
- $opt_in_message_appreciation = fs_text_inline( 'Connectivity to the licensing engine was successfully re-established. Automatic security & feature updates are now available through the WP Admin Dashboard.', 'premium-opt-in-message-appreciation', $slug );
35
-
36
- $opt_out_message_subtitle = sprintf( fs_text_inline( 'Warning: Opting out will block automatic updates', 'premium-opt-out-message-appreciation', $slug ), $fs->get_module_type() );
37
- $opt_out_message_usage_tracking = sprintf( fs_text_inline( 'Ongoing connectivity with the licensing engine is essential for receiving automatic security & feature updates of the paid product. To receive these updates, data like your license key, %1$s version, and WordPress version, is periodically sent to the server to check for updates. By opting out, you understand that your site won\'t receive automatic updates for %2$s from within the WP Admin Dashboard. This can put your site at risk, and we highly recommend to keep this connection active. If you do choose to opt-out, you\'ll need to check for %1$s updates and install them manually.', 'premium-opt-out-message-usage-tracking', $slug ), $fs->get_module_type(), $plugin_title );
38
-
39
- $primary_cta_label = fs_text_inline( 'I\'d like to keep automatic updates', 'premium-opt-out-cancel', $slug );
40
- } else {
41
- $opt_in_message_appreciation = sprintf( fs_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-in-message-appreciation', $slug ), $fs->get_module_type() );
42
-
43
- $opt_out_message_subtitle = $opt_in_message_appreciation;
44
- $opt_out_message_usage_tracking = sprintf( fs_text_inline( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking.", 'opt-out-message-usage-tracking', $slug ), $plugin_title );
45
- $primary_cta_label = fs_text_inline( 'On second thought - I want to continue helping', 'opt-out-cancel', $slug );
46
- }
47
-
48
- $opt_out_message_clicking_opt_out = sprintf(
49
- fs_text_inline( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.', 'opt-out-message-clicking-opt-out', $slug ),
50
- $plugin_title,
51
- sprintf(
52
- '<a href="%s" target="_blank" rel="noopener">%s</a>',
53
- 'https://freemius.com',
54
- 'freemius.com'
55
- )
56
- );
57
-
58
- $admin_notice_params = array(
59
- 'id' => '',
60
- 'slug' => $fs->get_id(),
61
- 'type' => 'success',
62
- 'sticky' => false,
63
- 'plugin' => $fs->get_plugin()->title,
64
- 'message' => $opt_in_message_appreciation
65
- );
66
-
67
- $admin_notice_html = fs_get_template( 'admin-notice.php', $admin_notice_params );
68
-
69
- $modal_content_html = "
70
- <h2" . ( $fs->is_premium() ? ' style="color: red"' : '' ) . ">{$opt_out_message_subtitle}</h2>
71
- <div class=\"notice notice-error inline opt-out-error-message\"><p></p></div>
72
- <p>{$opt_out_message_usage_tracking}</p>
73
- <p>{$opt_out_message_clicking_opt_out}</p>
74
- <label class=\"fs-permission-extensions\"><div class=\"fs-switch fs-small fs-round fs-" . ( $fs->is_extensions_tracking_allowed() ? 'on' : 'off' ) . "\"><div class=\"fs-toggle\"></div></div> " . fs_text_inline( 'Plugins & themes tracking' ) . " <span class=\"fs-switch-feedback success\"></span></label>";
75
-
76
- fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
77
- fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
78
- ?>
79
- <script type="text/javascript">
80
- (function( $ ) {
81
- $( document ).ready(function() {
82
- var modalContentHtml = <?php echo json_encode( $modal_content_html ) ?>,
83
- modalHtml =
84
- '<div class="fs-modal fs-modal-opt-out">'
85
- + ' <div class="fs-modal-dialog">'
86
- + ' <div class="fs-modal-header">'
87
- + ' <h4><?php echo esc_js( $opt_out_text ) ?></h4>'
88
- + ' </div>'
89
- + ' <div class="fs-modal-body">'
90
- + ' <div class="fs-modal-panel active">' + modalContentHtml + '</div>'
91
- + ' </div>'
92
- + ' <div class="fs-modal-footer">'
93
- + ' <button class="button <?php echo $fs->is_premium() ? 'button-primary warn' : 'button-secondary' ?> button-opt-out" tabindex="1"><?php echo esc_js( $opt_out_text ) ?></button>'
94
- + ' <button class="button <?php echo $fs->is_premium() ? 'button-secondary' : 'button-primary' ?> button-close" tabindex="2"><?php echo esc_js( $primary_cta_label ) ?></button>'
95
- + ' </div>'
96
- + ' </div>'
97
- + '</div>',
98
- $modal = $(modalHtml),
99
- $adminNotice = $( <?php echo json_encode( $admin_notice_html ) ?> ),
100
- action = '<?php echo $action ?>',
101
- actionLinkSelector = 'span.opt-in-or-opt-out.<?php echo $slug ?> a',
102
- $optOutButton = $modal.find( '.button-opt-out' ),
103
- $optOutErrorMessage = $modal.find( '.opt-out-error-message' ),
104
- $extensionsTracking = $modal.find( '.fs-permission-extensions' ),
105
- $body = $( 'body' ),
106
- moduleID = '<?php echo $fs->get_id() ?>';
107
-
108
- $modal.data( 'action', action );
109
- $modal.appendTo( $body );
110
-
111
- function registerActionLinkClick() {
112
- $body.on( 'click', actionLinkSelector, function( evt ) {
113
- evt.preventDefault();
114
-
115
- if ( 'stop_tracking' == $modal.data( 'action' ) ) {
116
- showModal();
117
- } else {
118
- optIn();
119
- }
120
-
121
- return false;
122
- });
123
- }
124
-
125
- function registerEventHandlers() {
126
- registerActionLinkClick();
127
-
128
- $modal.on( 'click', '.button-opt-out', function( evt ) {
129
- evt.preventDefault();
130
-
131
- if ( $( this ).hasClass( 'disabled' ) ) {
132
- return;
133
- }
134
-
135
- disableOptOutButton();
136
- optOut();
137
- });
138
-
139
- // If the user has clicked outside the window, close the modal.
140
- $modal.on( 'click', '.fs-close, .button-close', function() {
141
- closeModal();
142
- return false;
143
- });
144
- }
145
-
146
- <?php if ( $fs->is_registered() ) : ?>
147
- registerEventHandlers();
148
- <?php endif ?>
149
-
150
- function showModal() {
151
- resetModal();
152
-
153
- // Display the dialog box.
154
- $modal.addClass( 'active' );
155
- $body.addClass( 'has-fs-modal' );
156
- }
157
-
158
- function closeModal() {
159
- $modal.removeClass( 'active' );
160
- $body.removeClass( 'has-fs-modal' );
161
- }
162
-
163
- function resetOptOutButton() {
164
- enableOptOutButton();
165
- $optOutButton.text( <?php echo json_encode( $opt_out_text ) ?> );
166
- }
167
-
168
- function resetModal() {
169
- hideError();
170
- resetOptOutButton();
171
- }
172
-
173
- function optIn() {
174
- sendRequest();
175
- }
176
-
177
- function optOut() {
178
- sendRequest();
179
- }
180
-
181
- function sendRequest() {
182
- var $actionLink = $( actionLinkSelector );
183
-
184
- $.ajax({
185
- url: ajaxurl,
186
- method: 'POST',
187
- data: {
188
- action : ( 'stop_tracking' == action ?
189
- '<?php echo $fs->get_ajax_action( 'stop_tracking' ) ?>' :
190
- '<?php echo $fs->get_ajax_action( 'allow_tracking' ) ?>'
191
- ),
192
- security : ( 'stop_tracking' == action ?
193
- '<?php echo $fs->get_ajax_security( 'stop_tracking' ) ?>' :
194
- '<?php echo $fs->get_ajax_security( 'allow_tracking' ) ?>'
195
- ),
196
- module_id: moduleID,
197
- _wp_http_referer: '<?php echo $fs->current_page_url() ?>'
198
- },
199
- beforeSend: function() {
200
- if ( 'allow_tracking' == action ) {
201
- $actionLink.text( '<?php fs_esc_js_echo_inline( 'Opting in', 'opting-in', $slug ) ?>...' );
202
- } else {
203
- $optOutButton.text( '<?php fs_esc_js_echo_inline( 'Opting out', 'opting-out', $slug ) ?>...' );
204
- }
205
- },
206
- success: function( resultObj ) {
207
- if ( resultObj.success ) {
208
- if ( 'allow_tracking' == action ) {
209
- action = 'stop_tracking';
210
- $actionLink.text( '<?php echo esc_js( $opt_out_text ) ?>' );
211
- showOptInAppreciationMessageAndScrollToTop();
212
- } else {
213
- action = 'allow_tracking';
214
- $actionLink.text( '<?php echo esc_js( $opt_in_text ) ?>' );
215
- closeModal();
216
-
217
- if ( $adminNotice.length > 0 ) {
218
- $adminNotice.remove();
219
- }
220
- }
221
-
222
- $modal.data( 'action', action );
223
- } else {
224
- showError( resultObj.error );
225
- resetOptOutButton();
226
- }
227
- }
228
- });
229
- }
230
-
231
- var isUpdatingPermission = false;
232
- $extensionsTracking.on('click', function() {
233
- if (isUpdatingPermission) {
234
- return false;
235
- }
236
-
237
- isUpdatingPermission = true;
238
-
239
- var $switch = $extensionsTracking.find( '.fs-switch' ),
240
- $switchFeedback = $extensionsTracking.find( '.fs-switch-feedback' );
241
-
242
- $switch
243
- .toggleClass( 'fs-on' )
244
- .toggleClass( 'fs-off' );
245
-
246
- $switchFeedback.html( '<i class="fs-ajax-spinner"></i>' );
247
-
248
- $.ajax({
249
- url: ajaxurl,
250
- method: 'POST',
251
- data: {
252
- action : '<?php echo $fs->get_ajax_action( 'update_tracking_permission' ) ?>',
253
- security : '<?php echo $fs->get_ajax_security( 'update_tracking_permission' ) ?>',
254
- module_id : moduleID,
255
- _wp_http_referer: '<?php echo $fs->current_page_url() ?>',
256
- permission: 'extensions',
257
- is_enabled: $switch.hasClass('fs-on')
258
- },
259
- success: function( resultObj ) {
260
- if ( resultObj.success ) {
261
- $switchFeedback.html( '<i class="dashicons dashicons-yes"></i> <?php echo esc_js( fs_text_inline( 'Saved', 'saved', $slug ) ) ?>' )
262
- } else {
263
- $switch
264
- .toggleClass( 'fs-on' )
265
- .toggleClass( 'fs-off' );
266
- }
267
-
268
- isUpdatingPermission = false;
269
- }
270
- });
271
- });
272
-
273
- function enableOptOutButton() {
274
- $optOutButton.removeClass( 'disabled' );
275
- }
276
-
277
- function disableOptOutButton() {
278
- $optOutButton.addClass( 'disabled' );
279
- }
280
-
281
- function hideError() {
282
- $optOutErrorMessage.hide();
283
- }
284
-
285
- function showOptInAppreciationMessageAndScrollToTop() {
286
- $adminNotice.insertAfter( $( '#wpbody-content' ).find( ' > .wrap > h1' ) );
287
- window.scrollTo(0, 0);
288
- }
289
-
290
- function showError( msg ) {
291
- $optOutErrorMessage.find( ' > p' ).html( msg );
292
- $optOutErrorMessage.show();
293
- }
294
-
295
- <?php if ( $fs->is_theme() ) : ?>
296
- /**
297
- * Add opt-in/out button to the active theme's buttons collection
298
- * in the theme's extended details overlay.
299
- *
300
- * @author Vova Feldman (@svovaf)
301
- * @since 1.2.2.7
302
- */
303
- $('.theme-overlay').contentChange(function () {
304
- if (0 === $('.theme-overlay.active').length) {
305
- // Add opt-in/out button only to the currently active theme.
306
- return;
307
- }
308
-
309
- if ($('#fs_theme_opt_in_out').length > 0){
310
- // Button already there.
311
- return;
312
- }
313
-
314
- var label = (('stop_tracking' == action) ?
315
- '<?php echo esc_js( $opt_out_text ) ?>' :
316
- '<?php echo esc_js( $opt_in_text ) ?>'),
317
- href = (('stop_tracking' != action) ?
318
- '<?php echo ( $fs->is_registered() ? '' : esc_js( $reconnect_url ) ) ?>' :
319
- '');
320
-
321
- var $actionLink = $('<a id="fs_theme_opt_in_out" href="' + encodeURI(href) + '" class="button">' + label + '</a>');
322
-
323
- actionLinkSelector = '#fs_theme_opt_in_out';
324
-
325
- $modal.data( 'action', action );
326
-
327
- $('.theme-wrap .theme-actions .active-theme').append($actionLink);
328
-
329
- if ('' === href) {
330
- registerActionLinkClick();
331
- }
332
- });
333
- <?php endif ?>
334
- });
335
- })( jQuery );
336
- </script>
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.2.1.5
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * @var array $VARS
15
+ * @var Freemius $fs
16
+ */
17
+ $fs = freemius( $VARS['id'] );
18
+ $slug = $fs->get_slug();
19
+
20
+ $action = $fs->is_tracking_allowed() ?
21
+ 'stop_tracking' :
22
+ 'allow_tracking';
23
+
24
+ $reconnect_url = $fs->get_activation_url( array(
25
+ 'nonce' => wp_create_nonce( $fs->get_unique_affix() . '_reconnect' ),
26
+ 'fs_action' => ( $fs->get_unique_affix() . '_reconnect' ),
27
+ ) );
28
+
29
+ $plugin_title = "<strong>{$fs->get_plugin()->title}</strong>";
30
+ $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug );
31
+ $opt_in_text = fs_text_x_inline( 'Opt In', 'verb', 'opt-in', $slug );
32
+
33
+ if ( $fs->is_premium() ) {
34
+ $opt_in_message_appreciation = fs_text_inline( 'Connectivity to the licensing engine was successfully re-established. Automatic security & feature updates are now available through the WP Admin Dashboard.', 'premium-opt-in-message-appreciation', $slug );
35
+
36
+ $opt_out_message_subtitle = sprintf( fs_text_inline( 'Warning: Opting out will block automatic updates', 'premium-opt-out-message-appreciation', $slug ), $fs->get_module_type() );
37
+ $opt_out_message_usage_tracking = sprintf( fs_text_inline( 'Ongoing connectivity with the licensing engine is essential for receiving automatic security & feature updates of the paid product. To receive these updates, data like your license key, %1$s version, and WordPress version, is periodically sent to the server to check for updates. By opting out, you understand that your site won\'t receive automatic updates for %2$s from within the WP Admin Dashboard. This can put your site at risk, and we highly recommend to keep this connection active. If you do choose to opt-out, you\'ll need to check for %1$s updates and install them manually.', 'premium-opt-out-message-usage-tracking', $slug ), $fs->get_module_type(), $plugin_title );
38
+
39
+ $primary_cta_label = fs_text_inline( 'I\'d like to keep automatic updates', 'premium-opt-out-cancel', $slug );
40
+ } else {
41
+ $opt_in_message_appreciation = sprintf( fs_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-in-message-appreciation', $slug ), $fs->get_module_type() );
42
+
43
+ $opt_out_message_subtitle = $opt_in_message_appreciation;
44
+ $opt_out_message_usage_tracking = sprintf( fs_text_inline( "Usage tracking is done in the name of making %s better. Making a better user experience, prioritizing new features, and more good things. We'd really appreciate if you'll reconsider letting us continue with the tracking.", 'opt-out-message-usage-tracking', $slug ), $plugin_title );
45
+ $primary_cta_label = fs_text_inline( 'On second thought - I want to continue helping', 'opt-out-cancel', $slug );
46
+ }
47
+
48
+ $opt_out_message_clicking_opt_out = sprintf(
49
+ fs_text_inline( 'By clicking "Opt Out", we will no longer be sending any data from %s to %s.', 'opt-out-message-clicking-opt-out', $slug ),
50
+ $plugin_title,
51
+ sprintf(
52
+ '<a href="%s" target="_blank" rel="noopener">%s</a>',
53
+ 'https://freemius.com',
54
+ 'freemius.com'
55
+ )
56
+ );
57
+
58
+ $admin_notice_params = array(
59
+ 'id' => '',
60
+ 'slug' => $fs->get_id(),
61
+ 'type' => 'success',
62
+ 'sticky' => false,
63
+ 'plugin' => $fs->get_plugin()->title,
64
+ 'message' => $opt_in_message_appreciation
65
+ );
66
+
67
+ $admin_notice_html = fs_get_template( 'admin-notice.php', $admin_notice_params );
68
+
69
+ $modal_content_html = "
70
+ <h2" . ( $fs->is_premium() ? ' style="color: red"' : '' ) . ">{$opt_out_message_subtitle}</h2>
71
+ <div class=\"notice notice-error inline opt-out-error-message\"><p></p></div>
72
+ <p>{$opt_out_message_usage_tracking}</p>
73
+ <p>{$opt_out_message_clicking_opt_out}</p>
74
+ <label class=\"fs-permission-extensions\"><div class=\"fs-switch fs-small fs-round fs-" . ( $fs->is_extensions_tracking_allowed() ? 'on' : 'off' ) . "\"><div class=\"fs-toggle\"></div></div> " . fs_text_inline( 'Plugins & themes tracking' ) . " <span class=\"fs-switch-feedback success\"></span></label>";
75
+
76
+ fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
77
+ fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
78
+ ?>
79
+ <script type="text/javascript">
80
+ (function( $ ) {
81
+ $( document ).ready(function() {
82
+ var modalContentHtml = <?php echo json_encode( $modal_content_html ) ?>,
83
+ modalHtml =
84
+ '<div class="fs-modal fs-modal-opt-out">'
85
+ + ' <div class="fs-modal-dialog">'
86
+ + ' <div class="fs-modal-header">'
87
+ + ' <h4><?php echo esc_js( $opt_out_text ) ?></h4>'
88
+ + ' </div>'
89
+ + ' <div class="fs-modal-body">'
90
+ + ' <div class="fs-modal-panel active">' + modalContentHtml + '</div>'
91
+ + ' </div>'
92
+ + ' <div class="fs-modal-footer">'
93
+ + ' <button class="button <?php echo $fs->is_premium() ? 'button-primary warn' : 'button-secondary' ?> button-opt-out" tabindex="1"><?php echo esc_js( $opt_out_text ) ?></button>'
94
+ + ' <button class="button <?php echo $fs->is_premium() ? 'button-secondary' : 'button-primary' ?> button-close" tabindex="2"><?php echo esc_js( $primary_cta_label ) ?></button>'
95
+ + ' </div>'
96
+ + ' </div>'
97
+ + '</div>',
98
+ $modal = $(modalHtml),
99
+ $adminNotice = $( <?php echo json_encode( $admin_notice_html ) ?> ),
100
+ action = '<?php echo $action ?>',
101
+ actionLinkSelector = 'span.opt-in-or-opt-out.<?php echo $slug ?> a',
102
+ $optOutButton = $modal.find( '.button-opt-out' ),
103
+ $optOutErrorMessage = $modal.find( '.opt-out-error-message' ),
104
+ $extensionsTracking = $modal.find( '.fs-permission-extensions' ),
105
+ $body = $( 'body' ),
106
+ moduleID = '<?php echo $fs->get_id() ?>';
107
+
108
+ $modal.data( 'action', action );
109
+ $modal.appendTo( $body );
110
+
111
+ function registerActionLinkClick() {
112
+ $body.on( 'click', actionLinkSelector, function( evt ) {
113
+ evt.preventDefault();
114
+
115
+ if ( 'stop_tracking' == $modal.data( 'action' ) ) {
116
+ showModal();
117
+ } else {
118
+ optIn();
119
+ }
120
+
121
+ return false;
122
+ });
123
+ }
124
+
125
+ function registerEventHandlers() {
126
+ registerActionLinkClick();
127
+
128
+ $modal.on( 'click', '.button-opt-out', function( evt ) {
129
+ evt.preventDefault();
130
+
131
+ if ( $( this ).hasClass( 'disabled' ) ) {
132
+ return;
133
+ }
134
+
135
+ disableOptOutButton();
136
+ optOut();
137
+ });
138
+
139
+ // If the user has clicked outside the window, close the modal.
140
+ $modal.on( 'click', '.fs-close, .button-close', function() {
141
+ closeModal();
142
+ return false;
143
+ });
144
+ }
145
+
146
+ <?php if ( $fs->is_registered() ) : ?>
147
+ registerEventHandlers();
148
+ <?php endif ?>
149
+
150
+ function showModal() {
151
+ resetModal();
152
+
153
+ // Display the dialog box.
154
+ $modal.addClass( 'active' );
155
+ $body.addClass( 'has-fs-modal' );
156
+ }
157
+
158
+ function closeModal() {
159
+ $modal.removeClass( 'active' );
160
+ $body.removeClass( 'has-fs-modal' );
161
+ }
162
+
163
+ function resetOptOutButton() {
164
+ enableOptOutButton();
165
+ $optOutButton.text( <?php echo json_encode( $opt_out_text ) ?> );
166
+ }
167
+
168
+ function resetModal() {
169
+ hideError();
170
+ resetOptOutButton();
171
+ }
172
+
173
+ function optIn() {
174
+ sendRequest();
175
+ }
176
+
177
+ function optOut() {
178
+ sendRequest();
179
+ }
180
+
181
+ function sendRequest() {
182
+ var $actionLink = $( actionLinkSelector );
183
+
184
+ $.ajax({
185
+ url: ajaxurl,
186
+ method: 'POST',
187
+ data: {
188
+ action : ( 'stop_tracking' == action ?
189
+ '<?php echo $fs->get_ajax_action( 'stop_tracking' ) ?>' :
190
+ '<?php echo $fs->get_ajax_action( 'allow_tracking' ) ?>'
191
+ ),
192
+ security : ( 'stop_tracking' == action ?
193
+ '<?php echo $fs->get_ajax_security( 'stop_tracking' ) ?>' :
194
+ '<?php echo $fs->get_ajax_security( 'allow_tracking' ) ?>'
195
+ ),
196
+ module_id: moduleID,
197
+ _wp_http_referer: '<?php echo $fs->current_page_url() ?>'
198
+ },
199
+ beforeSend: function() {
200
+ if ( 'allow_tracking' == action ) {
201
+ $actionLink.text( '<?php fs_esc_js_echo_inline( 'Opting in', 'opting-in', $slug ) ?>...' );
202
+ } else {
203
+ $optOutButton.text( '<?php fs_esc_js_echo_inline( 'Opting out', 'opting-out', $slug ) ?>...' );
204
+ }
205
+ },
206
+ success: function( resultObj ) {
207
+ if ( resultObj.success ) {
208
+ if ( 'allow_tracking' == action ) {
209
+ action = 'stop_tracking';
210
+ $actionLink.text( '<?php echo esc_js( $opt_out_text ) ?>' );
211
+ showOptInAppreciationMessageAndScrollToTop();
212
+ } else {
213
+ action = 'allow_tracking';
214
+ $actionLink.text( '<?php echo esc_js( $opt_in_text ) ?>' );
215
+ closeModal();
216
+
217
+ if ( $adminNotice.length > 0 ) {
218
+ $adminNotice.remove();
219
+ }
220
+ }
221
+
222
+ $modal.data( 'action', action );
223
+ } else {
224
+ showError( resultObj.error );
225
+ resetOptOutButton();
226
+ }
227
+ }
228
+ });
229
+ }
230
+
231
+ var isUpdatingPermission = false;
232
+ $extensionsTracking.on('click', function() {
233
+ if (isUpdatingPermission) {
234
+ return false;
235
+ }
236
+
237
+ isUpdatingPermission = true;
238
+
239
+ var $switch = $extensionsTracking.find( '.fs-switch' ),
240
+ $switchFeedback = $extensionsTracking.find( '.fs-switch-feedback' );
241
+
242
+ $switch
243
+ .toggleClass( 'fs-on' )
244
+ .toggleClass( 'fs-off' );
245
+
246
+ $switchFeedback.html( '<i class="fs-ajax-spinner"></i>' );
247
+
248
+ $.ajax({
249
+ url: ajaxurl,
250
+ method: 'POST',
251
+ data: {
252
+ action : '<?php echo $fs->get_ajax_action( 'update_tracking_permission' ) ?>',
253
+ security : '<?php echo $fs->get_ajax_security( 'update_tracking_permission' ) ?>',
254
+ module_id : moduleID,
255
+ _wp_http_referer: '<?php echo $fs->current_page_url() ?>',
256
+ permission: 'extensions',
257
+ is_enabled: $switch.hasClass('fs-on')
258
+ },
259
+ success: function( resultObj ) {
260
+ if ( resultObj.success ) {
261
+ $switchFeedback.html( '<i class="dashicons dashicons-yes"></i> <?php echo esc_js( fs_text_inline( 'Saved', 'saved', $slug ) ) ?>' )
262
+ } else {
263
+ $switch
264
+ .toggleClass( 'fs-on' )
265
+ .toggleClass( 'fs-off' );
266
+ }
267
+
268
+ isUpdatingPermission = false;
269
+ }
270
+ });
271
+ });
272
+
273
+ function enableOptOutButton() {
274
+ $optOutButton.removeClass( 'disabled' );
275
+ }
276
+
277
+ function disableOptOutButton() {
278
+ $optOutButton.addClass( 'disabled' );
279
+ }
280
+
281
+ function hideError() {
282
+ $optOutErrorMessage.hide();
283
+ }
284
+
285
+ function showOptInAppreciationMessageAndScrollToTop() {
286
+ $adminNotice.insertAfter( $( '#wpbody-content' ).find( ' > .wrap > h1' ) );
287
+ window.scrollTo(0, 0);
288
+ }
289
+
290
+ function showError( msg ) {
291
+ $optOutErrorMessage.find( ' > p' ).html( msg );
292
+ $optOutErrorMessage.show();
293
+ }
294
+
295
+ <?php if ( $fs->is_theme() ) : ?>
296
+ /**
297
+ * Add opt-in/out button to the active theme's buttons collection
298
+ * in the theme's extended details overlay.
299
+ *
300
+ * @author Vova Feldman (@svovaf)
301
+ * @since 1.2.2.7
302
+ */
303
+ $('.theme-overlay').contentChange(function () {
304
+ if (0 === $('.theme-overlay.active').length) {
305
+ // Add opt-in/out button only to the currently active theme.
306
+ return;
307
+ }
308
+
309
+ if ($('#fs_theme_opt_in_out').length > 0){
310
+ // Button already there.
311
+ return;
312
+ }
313
+
314
+ var label = (('stop_tracking' == action) ?
315
+ '<?php echo esc_js( $opt_out_text ) ?>' :
316
+ '<?php echo esc_js( $opt_in_text ) ?>'),
317
+ href = (('stop_tracking' != action) ?
318
+ '<?php echo ( $fs->is_registered() ? '' : esc_js( $reconnect_url ) ) ?>' :
319
+ '');
320
+
321
+ var $actionLink = $('<a id="fs_theme_opt_in_out" href="' + encodeURI(href) + '" class="button">' + label + '</a>');
322
+
323
+ actionLinkSelector = '#fs_theme_opt_in_out';
324
+
325
+ $modal.data( 'action', action );
326
+
327
+ $('.theme-wrap .theme-actions .active-theme').append($actionLink);
328
+
329
+ if ('' === href) {
330
+ registerActionLinkClick();
331
+ }
332
+ });
333
+ <?php endif ?>
334
+ });
335
+ })( jQuery );
336
+ </script>
freemius/templates/forms/user-change.php CHANGED
@@ -1,296 +1,296 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 2.3.2
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * @var array $VARS
15
- *
16
- * @var Freemius $fs
17
- */
18
- $fs = freemius( $VARS['id'] );
19
- $slug = $fs->get_slug();
20
-
21
- /**
22
- * @var object[] $license_owners
23
- */
24
- $license_owners = $VARS['license_owners'];
25
-
26
- $change_user_message = fs_text_inline( 'By changing the user, you agree to transfer the account ownership to:', 'change-user--message', $slug );
27
- $header_title = fs_text_inline( 'Change User', 'change-user', $slug );
28
- $user_change_button_text = fs_text_inline( 'I Agree - Change User', 'agree-change-user', $slug );
29
- $other_text = fs_text_inline( 'Other', 'other', $slug );
30
- $enter_email_address_placeholder_text = fs_text_inline( 'Enter email address', 'enter-email-address', $slug );
31
-
32
- $user_change_options_html = <<< HTML
33
- <div class="fs-user-change-options-container">
34
- <table>
35
- <tbody>
36
- HTML;
37
-
38
- foreach ( $license_owners as $license_owner ) {
39
- $user_change_options_html .= <<< HTML
40
- <tr class="fs-email-address-container">
41
- <td><input id="fs_email_address_{$license_owner->id}" type="radio" name="fs_email_address" value="{$license_owner->id}"></td>
42
- <td><label for="fs_email_address_{$license_owner->id}">{$license_owner->email}</label></td>
43
- </tr>
44
- HTML;
45
- }
46
-
47
- $user_change_options_html .= <<< HTML
48
- <tr>
49
- <td><input id="fs_other_email_address_radio" type="radio" name="fs_email_address" value="other"></td>
50
- <td class="fs-other-email-address-container">
51
- <div>
52
- <label for="fs_email_address">{$other_text}: </label>
53
- <div>
54
- <input id="fs_other_email_address_text_field" class="fs-email-address" type="text" placeholder="{$enter_email_address_placeholder_text}" tabindex="1">
55
- </div>
56
- </div>
57
- </td>
58
- </tr>
59
- </tbody>
60
- </table>
61
- </div>
62
- HTML;
63
-
64
- $modal_content_html = <<< HTML
65
- <div class="notice notice-error inline fs-change-user-result-message"><p></p></div>
66
- <p>{$change_user_message}</p>
67
- {$user_change_options_html}
68
- HTML;
69
-
70
- fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
71
- ?>
72
- <script type="text/javascript">
73
- (function( $ ) {
74
- $( document ).ready(function() {
75
- var modalContentHtml = <?php echo json_encode( $modal_content_html ) ?>,
76
- modalHtml =
77
- '<div class="fs-modal fs-modal-change-user fs-modal-change-user-<?php echo $fs->get_unique_affix() ?>">'
78
- + ' <div class="fs-modal-dialog">'
79
- + ' <div class="fs-modal-header">'
80
- + ' <h4><?php echo esc_js( $header_title ) ?></h4>'
81
- + ' <a href="!#" class="fs-close"><i class="dashicons dashicons-no" title="<?php echo esc_js( fs_text_x_inline( 'Dismiss', 'close window', 'dismiss', $slug ) ) ?>"></i></a>'
82
- + ' </div>'
83
- + ' <div class="fs-modal-body">'
84
- + ' <div class="fs-modal-panel active">' + modalContentHtml + '</div>'
85
- + ' </div>'
86
- + ' <div class="fs-modal-footer">'
87
- + ' <button class="button button-secondary button-close" tabindex="4"><?php fs_esc_js_echo_inline( 'Cancel', 'cancel', $slug ) ?></button>'
88
- + ' <button class="button button-primary fs-user-change-button" tabindex="3"><?php echo esc_js( $user_change_button_text ) ?></button>'
89
- + ' </div>'
90
- + ' </div>'
91
- + '</div>',
92
- $modal = $( modalHtml ),
93
- $userChangeButton = $modal.find( '.fs-user-change-button' ),
94
- $otherEmailAddressRadio = $modal.find( '#fs_other_email_address_radio' ),
95
- $changeUserResultMessage = $modal.find( '.fs-change-user-result-message' ),
96
- $otherEmailAddressContainer = $modal.find( '.fs-other-email-address-container' ),
97
- $otherEmailAddressTextField = $modal.find( '#fs_other_email_address_text_field' ),
98
- $licenseOwners = $modal.find( 'input[type="radio"][name="fs_email_address"]' );
99
-
100
- $modal.appendTo( $( 'body' ) );
101
-
102
- var previousEmailAddress = null;
103
-
104
- function registerEventHandlers() {
105
- $licenseOwners.change( function() {
106
- var otherEmailAddress = $otherEmailAddressTextField.val().trim(),
107
- otherEmailAddressIsSelected = isOtherEmailAddressSelected();
108
-
109
- if ( otherEmailAddressIsSelected ) {
110
- $otherEmailAddressTextField.focus();
111
- }
112
-
113
- if ( otherEmailAddress.length > 0 || ! otherEmailAddressIsSelected ) {
114
- enableUserChangeButton();
115
- } else {
116
- disableUserChangeButton();
117
- }
118
- } );
119
-
120
- $otherEmailAddressContainer.click( function () {
121
- $otherEmailAddressRadio.click();
122
- } );
123
-
124
- // Handle for the "Change User" button on the "Account" page.
125
- $( '#fs_change_user' ).click( function ( evt ) {
126
- evt.preventDefault();
127
-
128
- showModal( evt );
129
- } );
130
-
131
- /**
132
- * Disables the "Change User" button when the email address is empty.
133
- */
134
- $modal.on( 'keyup paste delete cut', 'input#fs_other_email_address_text_field', function () {
135
- setTimeout( function () {
136
- var emailAddress = $otherEmailAddressRadio.val().trim();
137
-
138
- if ( emailAddress === previousEmailAddress ) {
139
- return;
140
- }
141
-
142
- if ( '' === emailAddress ) {
143
- disableUserChangeButton();
144
- } else {
145
- enableUserChangeButton();
146
- }
147
-
148
- previousEmailAddress = emailAddress;
149
- }, 100 );
150
- } ).focus();
151
-
152
- $modal.on( 'input propertychange', 'input#fs_other_email_address_text_field', function () {
153
- var emailAddress = $( this ).val().trim();
154
-
155
- /**
156
- * If email address is not empty, enable the "Change User" button.
157
- */
158
- if ( emailAddress.length > 0 ) {
159
- enableUserChangeButton();
160
- }
161
- } );
162
-
163
- $modal.on( 'blur', 'input#fs_other_email_address_text_field', function( evt ) {
164
- var emailAddress = $( this ).val().trim();
165
-
166
- /**
167
- * If email address is empty, disable the "Change User" button.
168
- */
169
- if ( 0 === emailAddress.length ) {
170
- disableUserChangeButton();
171
- }
172
- } );
173
-
174
- $modal.on( 'click', '.fs-user-change-button', function ( evt ) {
175
- evt.preventDefault();
176
-
177
- if ( $( this ).hasClass( 'disabled' ) ) {
178
- return;
179
- }
180
-
181
- var emailAddress = '',
182
- licenseOwnerID = null;
183
-
184
- if ( ! isOtherEmailAddressSelected() ) {
185
- licenseOwnerID = $licenseOwners.filter( ':checked' ).val();
186
- } else {
187
- emailAddress = $otherEmailAddressTextField.val().trim();
188
-
189
- if ( 0 === emailAddress.length ) {
190
- return;
191
- }
192
- }
193
-
194
- disableUserChangeButton();
195
-
196
- $.ajax( {
197
- url : ajaxurl,
198
- method : 'POST',
199
- data : {
200
- action : '<?php echo $fs->get_ajax_action( 'change_user' ) ?>',
201
- security : '<?php echo $fs->get_ajax_security( 'change_user' ) ?>',
202
- email_address: emailAddress,
203
- user_id : licenseOwnerID,
204
- module_id : '<?php echo $fs->get_id() ?>'
205
- },
206
- beforeSend: function () {
207
- $userChangeButton
208
- .text( '<?php fs_esc_js_echo_inline( 'Changing user, please wait', 'changing-user-please-wait', $slug ) ?>...' )
209
- .prepend('<i class="fs-ajax-spinner"></i>');
210
-
211
- $(document.body).css({'cursor': 'wait'});
212
- },
213
- success : function( result ) {
214
- if ( result.success ) {
215
- // Redirect to the "Account" page.
216
- window.location.reload();
217
- } else {
218
- $(document.body).css({'cursor': 'auto'});
219
-
220
- showError( result.error.message ? result.error.message : result.error );
221
- resetUserChangeButton();
222
- }
223
- },
224
- error : function () {
225
- $(document.body).css({'cursor': 'auto'});
226
-
227
- showError( '<?php fs_esc_js_echo_inline( 'Unexpected error, try again in 5 minutes. If the error persists, please contact support.', 'unexpected-error', $slug ) ?>' );
228
-
229
- resetUserChangeButton();
230
- }
231
- } );
232
- } );
233
-
234
- // If the user has clicked outside the window, close the modal.
235
- $modal.on( 'click', '.fs-close, .button-secondary', function () {
236
- closeModal();
237
- return false;
238
- } );
239
- }
240
-
241
- registerEventHandlers();
242
-
243
- /**
244
- * @returns {Boolean}
245
- */
246
- function isOtherEmailAddressSelected() {
247
- return ( 'other' === $licenseOwners.filter( ':checked' ).val() );
248
- }
249
-
250
- function showModal() {
251
- resetModal();
252
-
253
- // Display the dialog box.
254
- $modal.addClass( 'active' );
255
- $( 'body' ).addClass( 'has-fs-modal' );
256
-
257
- // Select the first radio button.
258
- $licenseOwners.get( 0 ).click();
259
-
260
- $otherEmailAddressTextField.val( '' );
261
- }
262
-
263
- function closeModal() {
264
- $modal.removeClass( 'active' );
265
- $( 'body' ).removeClass( 'has-fs-modal' );
266
- }
267
-
268
- function resetUserChangeButton() {
269
- enableUserChangeButton();
270
- $userChangeButton.text( <?php echo json_encode( $user_change_button_text ) ?> );
271
- }
272
-
273
- function resetModal() {
274
- hideError();
275
- resetUserChangeButton();
276
- }
277
-
278
- function enableUserChangeButton() {
279
- $userChangeButton.removeClass( 'disabled' );
280
- }
281
-
282
- function disableUserChangeButton() {
283
- $userChangeButton.addClass( 'disabled' );
284
- }
285
-
286
- function hideError() {
287
- $changeUserResultMessage.hide();
288
- }
289
-
290
- function showError( msg ) {
291
- $changeUserResultMessage.find( ' > p' ).html( msg );
292
- $changeUserResultMessage.show();
293
- }
294
- });
295
- })( jQuery );
296
- </script>
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 2.3.2
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * @var array $VARS
15
+ *
16
+ * @var Freemius $fs
17
+ */
18
+ $fs = freemius( $VARS['id'] );
19
+ $slug = $fs->get_slug();
20
+
21
+ /**
22
+ * @var object[] $license_owners
23
+ */
24
+ $license_owners = $VARS['license_owners'];
25
+
26
+ $change_user_message = fs_text_inline( 'By changing the user, you agree to transfer the account ownership to:', 'change-user--message', $slug );
27
+ $header_title = fs_text_inline( 'Change User', 'change-user', $slug );
28
+ $user_change_button_text = fs_text_inline( 'I Agree - Change User', 'agree-change-user', $slug );
29
+ $other_text = fs_text_inline( 'Other', 'other', $slug );
30
+ $enter_email_address_placeholder_text = fs_text_inline( 'Enter email address', 'enter-email-address', $slug );
31
+
32
+ $user_change_options_html = <<< HTML
33
+ <div class="fs-user-change-options-container">
34
+ <table>
35
+ <tbody>
36
+ HTML;
37
+
38
+ foreach ( $license_owners as $license_owner ) {
39
+ $user_change_options_html .= <<< HTML
40
+ <tr class="fs-email-address-container">
41
+ <td><input id="fs_email_address_{$license_owner->id}" type="radio" name="fs_email_address" value="{$license_owner->id}"></td>
42
+ <td><label for="fs_email_address_{$license_owner->id}">{$license_owner->email}</label></td>
43
+ </tr>
44
+ HTML;
45
+ }
46
+
47
+ $user_change_options_html .= <<< HTML
48
+ <tr>
49
+ <td><input id="fs_other_email_address_radio" type="radio" name="fs_email_address" value="other"></td>
50
+ <td class="fs-other-email-address-container">
51
+ <div>
52
+ <label for="fs_email_address">{$other_text}: </label>
53
+ <div>
54
+ <input id="fs_other_email_address_text_field" class="fs-email-address" type="text" placeholder="{$enter_email_address_placeholder_text}" tabindex="1">
55
+ </div>
56
+ </div>
57
+ </td>
58
+ </tr>
59
+ </tbody>
60
+ </table>
61
+ </div>
62
+ HTML;
63
+
64
+ $modal_content_html = <<< HTML
65
+ <div class="notice notice-error inline fs-change-user-result-message"><p></p></div>
66
+ <p>{$change_user_message}</p>
67
+ {$user_change_options_html}
68
+ HTML;
69
+
70
+ fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
71
+ ?>
72
+ <script type="text/javascript">
73
+ (function( $ ) {
74
+ $( document ).ready(function() {
75
+ var modalContentHtml = <?php echo json_encode( $modal_content_html ) ?>,
76
+ modalHtml =
77
+ '<div class="fs-modal fs-modal-change-user fs-modal-change-user-<?php echo $fs->get_unique_affix() ?>">'
78
+ + ' <div class="fs-modal-dialog">'
79
+ + ' <div class="fs-modal-header">'
80
+ + ' <h4><?php echo esc_js( $header_title ) ?></h4>'
81
+ + ' <a href="!#" class="fs-close"><i class="dashicons dashicons-no" title="<?php echo esc_js( fs_text_x_inline( 'Dismiss', 'close window', 'dismiss', $slug ) ) ?>"></i></a>'
82
+ + ' </div>'
83
+ + ' <div class="fs-modal-body">'
84
+ + ' <div class="fs-modal-panel active">' + modalContentHtml + '</div>'
85
+ + ' </div>'
86
+ + ' <div class="fs-modal-footer">'
87
+ + ' <button class="button button-secondary button-close" tabindex="4"><?php fs_esc_js_echo_inline( 'Cancel', 'cancel', $slug ) ?></button>'
88
+ + ' <button class="button button-primary fs-user-change-button" tabindex="3"><?php echo esc_js( $user_change_button_text ) ?></button>'
89
+ + ' </div>'
90
+ + ' </div>'
91
+ + '</div>',
92
+ $modal = $( modalHtml ),
93
+ $userChangeButton = $modal.find( '.fs-user-change-button' ),
94
+ $otherEmailAddressRadio = $modal.find( '#fs_other_email_address_radio' ),
95
+ $changeUserResultMessage = $modal.find( '.fs-change-user-result-message' ),
96
+ $otherEmailAddressContainer = $modal.find( '.fs-other-email-address-container' ),
97
+ $otherEmailAddressTextField = $modal.find( '#fs_other_email_address_text_field' ),
98
+ $licenseOwners = $modal.find( 'input[type="radio"][name="fs_email_address"]' );
99
+
100
+ $modal.appendTo( $( 'body' ) );
101
+
102
+ var previousEmailAddress = null;
103
+
104
+ function registerEventHandlers() {
105
+ $licenseOwners.change( function() {
106
+ var otherEmailAddress = $otherEmailAddressTextField.val().trim(),
107
+ otherEmailAddressIsSelected = isOtherEmailAddressSelected();
108
+
109
+ if ( otherEmailAddressIsSelected ) {
110
+ $otherEmailAddressTextField.focus();
111
+ }
112
+
113
+ if ( otherEmailAddress.length > 0 || ! otherEmailAddressIsSelected ) {
114
+ enableUserChangeButton();
115
+ } else {
116
+ disableUserChangeButton();
117
+ }
118
+ } );
119
+
120
+ $otherEmailAddressContainer.click( function () {
121
+ $otherEmailAddressRadio.click();
122
+ } );
123
+
124
+ // Handle for the "Change User" button on the "Account" page.
125
+ $( '#fs_change_user' ).click( function ( evt ) {
126
+ evt.preventDefault();
127
+
128
+ showModal( evt );
129
+ } );
130
+
131
+ /**
132
+ * Disables the "Change User" button when the email address is empty.
133
+ */
134
+ $modal.on( 'keyup paste delete cut', 'input#fs_other_email_address_text_field', function () {
135
+ setTimeout( function () {
136
+ var emailAddress = $otherEmailAddressRadio.val().trim();
137
+
138
+ if ( emailAddress === previousEmailAddress ) {
139
+ return;
140
+ }
141
+
142
+ if ( '' === emailAddress ) {
143
+ disableUserChangeButton();
144
+ } else {
145
+ enableUserChangeButton();
146
+ }
147
+
148
+ previousEmailAddress = emailAddress;
149
+ }, 100 );
150
+ } ).focus();
151
+
152
+ $modal.on( 'input propertychange', 'input#fs_other_email_address_text_field', function () {
153
+ var emailAddress = $( this ).val().trim();
154
+
155
+ /**
156
+ * If email address is not empty, enable the "Change User" button.
157
+ */
158
+ if ( emailAddress.length > 0 ) {
159
+ enableUserChangeButton();
160
+ }
161
+ } );
162
+
163
+ $modal.on( 'blur', 'input#fs_other_email_address_text_field', function( evt ) {
164
+ var emailAddress = $( this ).val().trim();
165
+
166
+ /**
167
+ * If email address is empty, disable the "Change User" button.
168
+ */
169
+ if ( 0 === emailAddress.length ) {
170
+ disableUserChangeButton();
171
+ }
172
+ } );
173
+
174
+ $modal.on( 'click', '.fs-user-change-button', function ( evt ) {
175
+ evt.preventDefault();
176
+
177
+ if ( $( this ).hasClass( 'disabled' ) ) {
178
+ return;
179
+ }
180
+
181
+ var emailAddress = '',
182
+ licenseOwnerID = null;
183
+
184
+ if ( ! isOtherEmailAddressSelected() ) {
185
+ licenseOwnerID = $licenseOwners.filter( ':checked' ).val();
186
+ } else {
187
+ emailAddress = $otherEmailAddressTextField.val().trim();
188
+
189
+ if ( 0 === emailAddress.length ) {
190
+ return;
191
+ }
192
+ }
193
+
194
+ disableUserChangeButton();
195
+
196
+ $.ajax( {
197
+ url : ajaxurl,
198
+ method : 'POST',
199
+ data : {
200
+ action : '<?php echo $fs->get_ajax_action( 'change_user' ) ?>',
201
+ security : '<?php echo $fs->get_ajax_security( 'change_user' ) ?>',
202
+ email_address: emailAddress,
203
+ user_id : licenseOwnerID,
204
+ module_id : '<?php echo $fs->get_id() ?>'
205
+ },
206
+ beforeSend: function () {
207
+ $userChangeButton
208
+ .text( '<?php fs_esc_js_echo_inline( 'Changing user, please wait', 'changing-user-please-wait', $slug ) ?>...' )
209
+ .prepend('<i class="fs-ajax-spinner"></i>');
210
+
211
+ $(document.body).css({'cursor': 'wait'});
212
+ },
213
+ success : function( result ) {
214
+ if ( result.success ) {
215
+ // Redirect to the "Account" page.
216
+ window.location.reload();
217
+ } else {
218
+ $(document.body).css({'cursor': 'auto'});
219
+
220
+ showError( result.error.message ? result.error.message : result.error );
221
+ resetUserChangeButton();
222
+ }
223
+ },
224
+ error : function () {
225
+ $(document.body).css({'cursor': 'auto'});
226
+
227
+ showError( '<?php fs_esc_js_echo_inline( 'Unexpected error, try again in 5 minutes. If the error persists, please contact support.', 'unexpected-error', $slug ) ?>' );
228
+
229
+ resetUserChangeButton();
230
+ }
231
+ } );
232
+ } );
233
+
234
+ // If the user has clicked outside the window, close the modal.
235
+ $modal.on( 'click', '.fs-close, .button-secondary', function () {
236
+ closeModal();
237
+ return false;
238
+ } );
239
+ }
240
+
241
+ registerEventHandlers();
242
+
243
+ /**
244
+ * @returns {Boolean}
245
+ */
246
+ function isOtherEmailAddressSelected() {
247
+ return ( 'other' === $licenseOwners.filter( ':checked' ).val() );
248
+ }
249
+
250
+ function showModal() {
251
+ resetModal();
252
+
253
+ // Display the dialog box.
254
+ $modal.addClass( 'active' );
255
+ $( 'body' ).addClass( 'has-fs-modal' );
256
+
257
+ // Select the first radio button.
258
+ $licenseOwners.get( 0 ).click();
259
+
260
+ $otherEmailAddressTextField.val( '' );
261
+ }
262
+
263
+ function closeModal() {
264
+ $modal.removeClass( 'active' );
265
+ $( 'body' ).removeClass( 'has-fs-modal' );
266
+ }
267
+
268
+ function resetUserChangeButton() {
269
+ enableUserChangeButton();
270
+ $userChangeButton.text( <?php echo json_encode( $user_change_button_text ) ?> );
271
+ }
272
+
273
+ function resetModal() {
274
+ hideError();
275
+ resetUserChangeButton();
276
+ }
277
+
278
+ function enableUserChangeButton() {
279
+ $userChangeButton.removeClass( 'disabled' );
280
+ }
281
+
282
+ function disableUserChangeButton() {
283
+ $userChangeButton.addClass( 'disabled' );
284
+ }
285
+
286
+ function hideError() {
287
+ $changeUserResultMessage.hide();
288
+ }
289
+
290
+ function showError( msg ) {
291
+ $changeUserResultMessage.find( ' > p' ).html( msg );
292
+ $changeUserResultMessage.show();
293
+ }
294
+ });
295
+ })( jQuery );
296
+ </script>
freemius/templates/partials/network-activation.php CHANGED
@@ -1,4 +1,9 @@
1
  <?php
 
 
 
 
 
2
  /**
3
  * @var array $VARS
4
  * @var Freemius $fs
@@ -86,4 +91,4 @@
86
  </tbody>
87
  </table>
88
  </div>
89
- </div>
1
  <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit;
5
+ }
6
+
7
  /**
8
  * @var array $VARS
9
  * @var Freemius $fs
91
  </tbody>
92
  </table>
93
  </div>
94
+ </div>
freemius/templates/sticky-admin-notice-js.php CHANGED
@@ -23,7 +23,9 @@
23
 
24
  notice.fadeOut( 'fast', function() {
25
  var data = {
26
- action : 'fs_dismiss_notice_action_' + ajaxActionSuffix,
 
 
27
  message_id: id
28
  };
29
 
@@ -36,4 +38,4 @@
36
  });
37
  });
38
  });
39
- </script>
23
 
24
  notice.fadeOut( 'fast', function() {
25
  var data = {
26
+ action : 'fs_dismiss_notice_action_' + ajaxActionSuffix,
27
+ // As such we don't need to use `wp_json_encode` method but using it to follow wp.org guideline.
28
+ _wpnonce : <?php echo wp_json_encode( wp_create_nonce( 'fs_dismiss_notice_action' ) ); ?>,
29
  message_id: id
30
  };
31
 
38
  });
39
  });
40
  });
41
+ </script>
freemius/templates/tabs.php CHANGED
@@ -1,190 +1,190 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.2.2.7
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * @var array $VARS
15
- * @var Freemius $fs
16
- */
17
- $fs = freemius( $VARS['id'] );
18
-
19
- $slug = $fs->get_slug();
20
-
21
- $menu_items = $fs->get_menu_items();
22
-
23
- $show_settings_with_tabs = $fs->show_settings_with_tabs();
24
-
25
- $tabs = array();
26
- foreach ( $menu_items as $priority => $items ) {
27
- foreach ( $items as $item ) {
28
- if ( ! $item['show_submenu'] ) {
29
- $submenu_name = ('wp-support-forum' === $item['menu_slug']) ?
30
- 'support' :
31
- $item['menu_slug'];
32
-
33
- if ( 'pricing' === $submenu_name && ! $fs->is_pricing_page_visible() ) {
34
- continue;
35
- }
36
-
37
- if ( ! $show_settings_with_tabs || ! $fs->is_submenu_item_visible( $submenu_name, true ) ) {
38
- continue;
39
- }
40
- }
41
-
42
- $url = $fs->_get_admin_page_url( $item['menu_slug'] );
43
- $title = $item['menu_title'];
44
-
45
- $tab = array(
46
- 'label' => $title,
47
- 'href' => $url,
48
- 'slug' => $item['menu_slug'],
49
- );
50
-
51
- if ( 'pricing' === $item['menu_slug'] && $fs->is_in_trial_promotion() ) {
52
- $tab['href'] .= '&trial=true';
53
- }
54
-
55
- $tabs[] = $tab;
56
- }
57
- }
58
- ?>
59
- <script type="text/javascript">
60
- (function ($) {
61
- $(document).ready(function () {
62
- var $wrap = $('.wrap');
63
- if (0 === $wrap.length) {
64
- $wrap = $('<div class="wrap">');
65
- $wrap.insertBefore($('#wpbody-content .clear'));
66
- }
67
-
68
- var
69
- $tabsWrapper = $('.nav-tab-wrapper'),
70
- $tabs = $tabsWrapper.find('.nav-tab'),
71
- $tab = null;
72
-
73
- if (0 < $tabs.length) {
74
- // Tries to set $tab to the first inactive tab.
75
- for (var i = 0; i < $tabs.length; i++) {
76
- $tab = $($tabs[i]);
77
-
78
- if (!$tab.hasClass('nav-tab-active')) {
79
- break;
80
- }
81
- }
82
- }
83
-
84
- <?php if (0 < count( $tabs )) : ?>
85
- if (null == $tab) {
86
- // No tabs found, therefore, create new tabs section if required.
87
- var $h1 = $wrap.find('h1');
88
-
89
- $tabsWrapper = $('<h2 class="nav-tab-wrapper"></h2>');
90
-
91
- if (0 < $h1.length) {
92
- $tabsWrapper.insertAfter($h1);
93
- } else if (0 < $wrap.length) {
94
- $wrap.prepend($tabsWrapper);
95
- }
96
-
97
- $tab = $('<a href="#" class="nav-tab"></a>');
98
- }
99
-
100
- // Create a clone.
101
- $tab = $tab.clone();
102
- // Open in current page.
103
- $tab.removeAttr('target');
104
- $tab.removeClass('nav-tab-active');
105
- $tab.addClass('fs-tab');
106
- $tab.addClass('<?php echo $fs->get_unique_affix() ?>');
107
-
108
- var $tabClone = null;
109
-
110
- <?php $freemius_context_page = null ?>
111
-
112
- <?php foreach ($tabs as $tab) : ?>
113
- <?php $is_support_tab = ( 'wp-support-forum' == $tab['slug'] ) ?>
114
- // Add the Freemius tabs.
115
- $tabClone = $tab.clone();
116
- $tabClone.html(<?php echo json_encode( $tab['label'] ) ?>)
117
- .attr('href', '<?php echo $is_support_tab ? $fs->get_support_forum_url() : $tab['href'] ?>')
118
- .appendTo($tabsWrapper)
119
- // Remove any custom click events.
120
- .off('click', '**')
121
- .addClass('<?php echo $tab['slug'] ?>')
122
- // Avoid tab click triggering parent events.
123
- .click(function (e) {
124
- e.stopPropagation();
125
- });
126
-
127
- <?php if ($is_support_tab) : ?>
128
- // Open support in a new tab/page.
129
- $tabClone.attr('target', '_blank');
130
- <?php endif ?>
131
-
132
- <?php if ($fs->is_admin_page( $tab['slug'] )) : ?>
133
- <?php $freemius_context_page = $tab['slug'] ?>
134
- // Select the relevant Freemius tab.
135
- $tabs.removeClass('nav-tab-active');
136
- $tabClone.addClass('nav-tab-active');
137
-
138
- <?php if (in_array( $freemius_context_page, array( 'pricing', 'contact', 'checkout' ) )) : ?>
139
- // Add AJAX loader.
140
- $tabClone.prepend('<i class="fs-ajax-spinner"></i>');
141
- // Hide loader after content fully loaded.
142
- $('.wrap i' + 'frame').load(function () {
143
- $(".fs-ajax-spinner").hide();
144
- });
145
- <?php endif ?>
146
-
147
- // Fix URLs that are starting with a hashtag.
148
- $tabs.each(function (j, tab) {
149
- if (0 === $(tab).attr('href').indexOf('#')) {
150
- $(tab).attr('href', '<?php echo esc_js( $fs->main_menu_url() ) ?>' + $(tab).attr('href'));
151
- }
152
- });
153
- <?php endif ?>
154
- <?php endforeach ?>
155
-
156
- var selectTab = function ($tab) {
157
- $(window).load(function () {
158
- $tab.click();
159
-
160
- // Scroll to the top since the browser will auto scroll to the anchor.
161
- document.body.scrollTop = 0;
162
- document.body.scrollLeft = 0;
163
- // window.scrollTo(0,0);
164
- });
165
- };
166
-
167
- // If the URL is loaded with a hashtag, find the context tab and select it.
168
- if (window.location.hash) {
169
- for (var j = 0; j < $tabs.length; j++) {
170
- if (window.location.hash === $($tabs[j]).attr('href')) {
171
- selectTab($($tabs[j]));
172
- break;
173
- }
174
- }
175
- }
176
-
177
- <?php if (is_string( $freemius_context_page ) && in_array( $freemius_context_page, array(
178
- 'pricing',
179
- 'contact',
180
- 'checkout'
181
- ) )) : ?>
182
- // Add margin to the upper section of the tabs to give extra space for the HTTPS header.
183
- // @todo This code assumes that the wrapper style is fully loaded, if there's a stylesheet that is not loaded via the HTML head, it may cause unpredicted margin-top.
184
- var $tabsWrap = $tabsWrapper.parents('.wrap');
185
- $tabsWrap.css('marginTop', (parseInt($tabsWrap.css('marginTop'), 10) + 30) + 'px');
186
- <?php endif ?>
187
- <?php endif ?>
188
- });
189
- })(jQuery);
190
  </script>
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.2.2.7
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * @var array $VARS
15
+ * @var Freemius $fs
16
+ */
17
+ $fs = freemius( $VARS['id'] );
18
+
19
+ $slug = $fs->get_slug();
20
+
21
+ $menu_items = $fs->get_menu_items();
22
+
23
+ $show_settings_with_tabs = $fs->show_settings_with_tabs();
24
+
25
+ $tabs = array();
26
+ foreach ( $menu_items as $priority => $items ) {
27
+ foreach ( $items as $item ) {
28
+ if ( ! $item['show_submenu'] ) {
29
+ $submenu_name = ('wp-support-forum' === $item['menu_slug']) ?
30
+ 'support' :
31
+ $item['menu_slug'];
32
+
33
+ if ( 'pricing' === $submenu_name && ! $fs->is_pricing_page_visible() ) {
34
+ continue;
35
+ }
36
+
37
+ if ( ! $show_settings_with_tabs || ! $fs->is_submenu_item_visible( $submenu_name, true ) ) {
38
+ continue;
39
+ }
40
+ }
41
+
42
+ $url = $fs->_get_admin_page_url( $item['menu_slug'] );
43
+ $title = $item['menu_title'];
44
+
45
+ $tab = array(
46
+ 'label' => $title,
47
+ 'href' => $url,
48
+ 'slug' => $item['menu_slug'],
49
+ );
50
+
51
+ if ( 'pricing' === $item['menu_slug'] && $fs->is_in_trial_promotion() ) {
52
+ $tab['href'] .= '&trial=true';
53
+ }
54
+
55
+ $tabs[] = $tab;
56
+ }
57
+ }
58
+ ?>
59
+ <script type="text/javascript">
60
+ (function ($) {
61
+ $(document).ready(function () {
62
+ var $wrap = $('.wrap');
63
+ if (0 === $wrap.length) {
64
+ $wrap = $('<div class="wrap">');
65
+ $wrap.insertBefore($('#wpbody-content .clear'));
66
+ }
67
+
68
+ var
69
+ $tabsWrapper = $('.nav-tab-wrapper'),
70
+ $tabs = $tabsWrapper.find('.nav-tab'),
71
+ $tab = null;
72
+
73
+ if (0 < $tabs.length) {
74
+ // Tries to set $tab to the first inactive tab.
75
+ for (var i = 0; i < $tabs.length; i++) {
76
+ $tab = $($tabs[i]);
77
+
78
+ if (!$tab.hasClass('nav-tab-active')) {
79
+ break;
80
+ }
81
+ }
82
+ }
83
+
84
+ <?php if (0 < count( $tabs )) : ?>
85
+ if (null == $tab) {
86
+ // No tabs found, therefore, create new tabs section if required.
87
+ var $h1 = $wrap.find('h1');
88
+
89
+ $tabsWrapper = $('<h2 class="nav-tab-wrapper"></h2>');
90
+
91
+ if (0 < $h1.length) {
92
+ $tabsWrapper.insertAfter($h1);
93
+ } else if (0 < $wrap.length) {
94
+ $wrap.prepend($tabsWrapper);
95
+ }
96
+
97
+ $tab = $('<a href="#" class="nav-tab"></a>');
98
+ }
99
+
100
+ // Create a clone.
101
+ $tab = $tab.clone();
102
+ // Open in current page.
103
+ $tab.removeAttr('target');
104
+ $tab.removeClass('nav-tab-active');
105
+ $tab.addClass('fs-tab');
106
+ $tab.addClass('<?php echo $fs->get_unique_affix() ?>');
107
+
108
+ var $tabClone = null;
109
+
110
+ <?php $freemius_context_page = null ?>
111
+
112
+ <?php foreach ($tabs as $tab) : ?>
113
+ <?php $is_support_tab = ( 'wp-support-forum' == $tab['slug'] ) ?>
114
+ // Add the Freemius tabs.
115
+ $tabClone = $tab.clone();
116
+ $tabClone.html(<?php echo json_encode( $tab['label'] ) ?>)
117
+ .attr('href', '<?php echo $is_support_tab ? $fs->get_support_forum_url() : $tab['href'] ?>')
118
+ .appendTo($tabsWrapper)
119
+ // Remove any custom click events.
120
+ .off('click', '**')
121
+ .addClass('<?php echo $tab['slug'] ?>')
122
+ // Avoid tab click triggering parent events.
123
+ .click(function (e) {
124
+ e.stopPropagation();
125
+ });
126
+
127
+ <?php if ($is_support_tab) : ?>
128
+ // Open support in a new tab/page.
129
+ $tabClone.attr('target', '_blank');
130
+ <?php endif ?>
131
+
132
+ <?php if ($fs->is_admin_page( $tab['slug'] )) : ?>
133
+ <?php $freemius_context_page = $tab['slug'] ?>
134
+ // Select the relevant Freemius tab.
135
+ $tabs.removeClass('nav-tab-active');
136
+ $tabClone.addClass('nav-tab-active');
137
+
138
+ <?php if (in_array( $freemius_context_page, array( 'pricing', 'contact', 'checkout' ) )) : ?>
139
+ // Add AJAX loader.
140
+ $tabClone.prepend('<i class="fs-ajax-spinner"></i>');
141
+ // Hide loader after content fully loaded.
142
+ $('.wrap i' + 'frame').load(function () {
143
+ $(".fs-ajax-spinner").hide();
144
+ });
145
+ <?php endif ?>
146
+
147
+ // Fix URLs that are starting with a hashtag.
148
+ $tabs.each(function (j, tab) {
149
+ if (0 === $(tab).attr('href').indexOf('#')) {
150
+ $(tab).attr('href', '<?php echo esc_js( $fs->main_menu_url() ) ?>' + $(tab).attr('href'));
151
+ }
152
+ });
153
+ <?php endif ?>
154
+ <?php endforeach ?>
155
+
156
+ var selectTab = function ($tab) {
157
+ $(window).load(function () {
158
+ $tab.click();
159
+
160
+ // Scroll to the top since the browser will auto scroll to the anchor.
161
+ document.body.scrollTop = 0;
162
+ document.body.scrollLeft = 0;
163
+ // window.scrollTo(0,0);
164
+ });
165
+ };
166
+
167
+ // If the URL is loaded with a hashtag, find the context tab and select it.
168
+ if (window.location.hash) {
169
+ for (var j = 0; j < $tabs.length; j++) {
170
+ if (window.location.hash === $($tabs[j]).attr('href')) {
171
+ selectTab($($tabs[j]));
172
+ break;
173
+ }
174
+ }
175
+ }
176
+
177
+ <?php if (is_string( $freemius_context_page ) && in_array( $freemius_context_page, array(
178
+ 'pricing',
179
+ 'contact',
180
+ 'checkout'
181
+ ) )) : ?>
182
+ // Add margin to the upper section of the tabs to give extra space for the HTTPS header.
183
+ // @todo This code assumes that the wrapper style is fully loaded, if there's a stylesheet that is not loaded via the HTML head, it may cause unpredicted margin-top.
184
+ var $tabsWrap = $tabsWrapper.parents('.wrap');
185
+ $tabsWrap.css('marginTop', (parseInt($tabsWrap.css('marginTop'), 10) + 30) + 'px');
186
+ <?php endif ?>
187
+ <?php endif ?>
188
+ });
189
+ })(jQuery);
190
  </script>
lib/src/blocks/extend-blocks.js CHANGED
@@ -169,7 +169,7 @@ addFilter(
169
  </PanelRow>
170
  <PanelRow className="simple-sitemap">
171
  <TextControl
172
- label="Exclude posts"
173
  help="Enter comma separated list of post ID's"
174
  value={ exclude }
175
  onChange={ ( value ) => {
@@ -179,7 +179,7 @@ addFilter(
179
  </PanelRow>
180
  <PanelRow className="simple-sitemap">
181
  <TextControl
182
- label="Include posts"
183
  help="Enter comma separated list of post ID's"
184
  value={ include }
185
  onChange={ ( value ) => {
169
  </PanelRow>
170
  <PanelRow className="simple-sitemap">
171
  <TextControl
172
+ label="Exclude post IDs from sitemap"
173
  help="Enter comma separated list of post ID's"
174
  value={ exclude }
175
  onChange={ ( value ) => {
179
  </PanelRow>
180
  <PanelRow className="simple-sitemap">
181
  <TextControl
182
+ label="Include post IDs in sitemap"
183
  help="Enter comma separated list of post ID's"
184
  value={ include }
185
  onChange={ ( value ) => {
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_i
4
  Tags: seo sitemap, html, sitemap, html sitemap, responsive sitemap
5
  Requires at least: 5.0
6
  Tested up to: 5.9
7
- Stable tag: 3.5.4
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -159,6 +159,10 @@ No. This plugin is for generating an HTML sitemap only.
159
 
160
  == Changelog ==
161
 
 
 
 
 
162
  = 3.5.4 - November 23, 2021 =
163
 
164
  * Tidied up configuration files. Added .eslintignore to the list of files to auto-remove from deployed free/pro plugin versions.
4
  Tags: seo sitemap, html, sitemap, html sitemap, responsive sitemap
5
  Requires at least: 5.0
6
  Tested up to: 5.9
7
+ Stable tag: 3.5.5
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
159
 
160
  == Changelog ==
161
 
162
+ = 3.5.5 - March 4, 2022 =
163
+
164
+ * Security update. Fixes minor issues in the Freemius SDK (that handle licensing and plugin updates).
165
+
166
  = 3.5.4 - November 23, 2021 =
167
 
168
  * Tidied up configuration files. Added .eslintignore to the list of files to auto-remove from deployed free/pro plugin versions.
simple-sitemap.php CHANGED
@@ -4,7 +4,7 @@
4
  Plugin Name: Simple Sitemap
5
  Plugin URI: http://wordpress.org/plugins/simple-sitemap/
6
  Description: HTML sitemap to display content as a single linked list of posts, pages, or custom post types. You can even display posts in groups sorted by taxonomy!
7
- Version: 3.5.4
8
  Author: David Gwyer
9
  Author URI: http://www.wpgoplugins.com
10
  Text Domain: simple-sitemap
4
  Plugin Name: Simple Sitemap
5
  Plugin URI: http://wordpress.org/plugins/simple-sitemap/
6
  Description: HTML sitemap to display content as a single linked list of posts, pages, or custom post types. You can even display posts in groups sorted by taxonomy!
7
+ Version: 3.5.5
8
  Author: David Gwyer
9
  Author URI: http://www.wpgoplugins.com
10
  Text Domain: simple-sitemap