Delete Duplicate Posts - Version 4.7.7

Version Description

  • Fix - Now you can choose how many duplicates to see in the interface.
  • Tested with WordPress 6.0.
  • Updated language file for translations.
Download this release

Release Info

Developer lkoudal
Plugin Icon 128x128 Delete Duplicate Posts
Version 4.7.7
Comparing to
See all releases

Code changes from version 4.7.6 to 4.7.7

css/delete-duplicate-posts-min.css CHANGED
@@ -204,11 +204,10 @@
204
  }
205
 
206
  #sidebar-container .sidebarrow img.logo {
207
-
208
- width: 50%;
209
  margin-bottom: 10px;
210
  display: block;
211
-
 
212
  }
213
 
214
  #log {
204
  }
205
 
206
  #sidebar-container .sidebarrow img.logo {
 
 
207
  margin-bottom: 10px;
208
  display: block;
209
+ float: left;
210
+ width: 100%;
211
  }
212
 
213
  #log {
css/delete-duplicate-posts.css CHANGED
@@ -204,11 +204,10 @@
204
  }
205
 
206
  #sidebar-container .sidebarrow img.logo {
207
-
208
- width: 50%;
209
  margin-bottom: 10px;
210
  display: block;
211
-
 
212
  }
213
 
214
  #log {
204
  }
205
 
206
  #sidebar-container .sidebarrow img.logo {
 
 
207
  margin-bottom: 10px;
208
  display: block;
209
+ float: left;
210
+ width: 100%;
211
  }
212
 
213
  #log {
delete-duplicate-posts.php CHANGED
@@ -5,11 +5,11 @@ Plugin Name: Delete Duplicate Posts
5
  Plugin Script: delete-duplicate-posts.php
6
  Plugin URI: https://cleverplugins.com
7
  Description: Remove duplicate blogposts on your blog! Searches and removes duplicate posts and their post meta tags. You can delete posts, pages and other Custom Post Types enabled on your website.
8
- Version: 4.7.6
9
  Author: cleverplugins.com
10
  Author URI: https://cleverplugins.com
11
  Min WP Version: 4.7
12
- Max WP Version: 5.8.2
13
  Text Domain: delete-duplicate-posts
14
  Domain Path: /languages
15
  */
@@ -521,13 +521,14 @@ if ( !class_exists( 'Delete_Duplicate_Posts' ) ) {
521
  global $wpdb ;
522
  $table_name = $wpdb->prefix . 'posts';
523
  $resultslimit = $options['ddp_resultslimit'];
524
-
525
- if ( 101 < $resultslimit ) {
526
- $viewlimit = $resultslimit;
527
- } else {
528
- $viewlimit = 50;
529
- }
530
-
 
531
  $ddp_pts_arr = $options['ddp_pts'];
532
 
533
  if ( isset( $ddp_pts_arr ) && is_array( $ddp_pts_arr ) ) {
@@ -571,7 +572,7 @@ if ( !class_exists( 'Delete_Duplicate_Posts' ) ) {
571
  }
572
  $thisquery = "SELECT t1.ID, t1.post_title, t1.post_type, t1.post_status, save_this_post_id\n\t\t\t\t\t\t\t\t\t\t\t\t\tFROM {$table_name} AS t1 INNER JOIN (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tSELECT post_title, " . $minmax . " AS save_this_post_id\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tFROM {$table_name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tWHERE post_type IN (" . $ddp_pts . ')
573
  AND post_status IN (' . $post_stati . ')
574
- GROUP BY post_title HAVING COUNT(*)>1 LIMIT 0,100
575
  ) AS t2 ON t1.post_title = t2.post_title
576
  AND post_status IN (' . $post_stati . ')
577
  ORDER BY t1.post_title, t1.post_date DESC';
@@ -1363,8 +1364,7 @@ if ( !class_exists( 'Delete_Duplicate_Posts' ) ) {
1363
  foreach ( $post_types as $pt ) {
1364
  $checked = array_search( $pt, $checked_post_types, true );
1365
  ?>
1366
- <li>
1367
- <input type="checkbox" name="ddp_pts[]" id="ddp_pt-<?php
1368
  echo esc_attr( $step ) ;
1369
  ?>" value="<?php
1370
  echo esc_html( $pt ) ;
@@ -1373,8 +1373,7 @@ if ( !class_exists( 'Delete_Duplicate_Posts' ) ) {
1373
  if ( false !== $checked ) {
1374
  echo ' checked' ;
1375
  }
1376
- ?>
1377
- />
1378
  <label for="ddp_pt-<?php
1379
  echo esc_attr( $step ) ;
1380
  ?>"><?php
@@ -1389,8 +1388,7 @@ if ( !class_exists( 'Delete_Duplicate_Posts' ) ) {
1389
  }
1390
  // translators: Total number of deleted duplicates
1391
  echo '<small>' . sprintf( esc_html__( '(%s total found)', 'delete-duplicate-posts' ), esc_html( number_format_i18n( $othercount ) ) ) . '</small>' ;
1392
- ?>
1393
- </li>
1394
  <?php
1395
  $step++;
1396
  }
5
  Plugin Script: delete-duplicate-posts.php
6
  Plugin URI: https://cleverplugins.com
7
  Description: Remove duplicate blogposts on your blog! Searches and removes duplicate posts and their post meta tags. You can delete posts, pages and other Custom Post Types enabled on your website.
8
+ Version: 4.7.7
9
  Author: cleverplugins.com
10
  Author URI: https://cleverplugins.com
11
  Min WP Version: 4.7
12
+ Max WP Version: 6.0
13
  Text Domain: delete-duplicate-posts
14
  Domain Path: /languages
15
  */
521
  global $wpdb ;
522
  $table_name = $wpdb->prefix . 'posts';
523
  $resultslimit = $options['ddp_resultslimit'];
524
+ $viewlimit = $resultslimit;
525
+ /*
526
+ if ( 101 < $resultslimit ) {
527
+ $viewlimit = $resultslimit;
528
+ } else {
529
+ $viewlimit = 50;
530
+ }
531
+ */
532
  $ddp_pts_arr = $options['ddp_pts'];
533
 
534
  if ( isset( $ddp_pts_arr ) && is_array( $ddp_pts_arr ) ) {
572
  }
573
  $thisquery = "SELECT t1.ID, t1.post_title, t1.post_type, t1.post_status, save_this_post_id\n\t\t\t\t\t\t\t\t\t\t\t\t\tFROM {$table_name} AS t1 INNER JOIN (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tSELECT post_title, " . $minmax . " AS save_this_post_id\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tFROM {$table_name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tWHERE post_type IN (" . $ddp_pts . ')
574
  AND post_status IN (' . $post_stati . ')
575
+ GROUP BY post_title HAVING COUNT(*)>1 ' . $resultsoutput . '
576
  ) AS t2 ON t1.post_title = t2.post_title
577
  AND post_status IN (' . $post_stati . ')
578
  ORDER BY t1.post_title, t1.post_date DESC';
1364
  foreach ( $post_types as $pt ) {
1365
  $checked = array_search( $pt, $checked_post_types, true );
1366
  ?>
1367
+ <li><input type="checkbox" name="ddp_pts[]" id="ddp_pt-<?php
 
1368
  echo esc_attr( $step ) ;
1369
  ?>" value="<?php
1370
  echo esc_html( $pt ) ;
1373
  if ( false !== $checked ) {
1374
  echo ' checked' ;
1375
  }
1376
+ ?>/>
 
1377
  <label for="ddp_pt-<?php
1378
  echo esc_attr( $step ) ;
1379
  ?>"><?php
1388
  }
1389
  // translators: Total number of deleted duplicates
1390
  echo '<small>' . sprintf( esc_html__( '(%s total found)', 'delete-duplicate-posts' ), esc_html( number_format_i18n( $othercount ) ) ) . '</small>' ;
1391
+ ?></li>
 
1392
  <?php
1393
  $step++;
1394
  }
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-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/templates/account.php CHANGED
@@ -1,1098 +1,1098 @@
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 $fs
16
- */
17
- $fs = freemius( $VARS['id'] );
18
-
19
- $slug = $fs->get_slug();
20
-
21
- /**
22
- * @var FS_Plugin_Tag $update
23
- */
24
- $update = $fs->has_release_on_freemius() ?
25
- $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ) :
26
- null;
27
-
28
- if ( is_object($update) ) {
29
- /**
30
- * This logic is particularly required for multisite environment.
31
- * If a module is site activated (not network) and not on the main site,
32
- * the module will NOT be executed on the network level, therefore, the
33
- * custom updates logic will not be executed as well, so unless we force
34
- * the injection of the update into the updates transient, premium updates
35
- * will not work.
36
- *
37
- * @author Vova Feldman (@svovaf)
38
- * @since 2.0.0
39
- */
40
- $updater = FS_Plugin_Updater::instance( $fs );
41
- $updater->set_update_data( $update );
42
- }
43
-
44
- $is_paying = $fs->is_paying();
45
- $user = $fs->get_user();
46
- $site = $fs->get_site();
47
- $name = $user->get_name();
48
- $license = $fs->_get_license();
49
- $is_data_debug_mode = $fs->is_data_debug_mode();
50
- $is_whitelabeled = $fs->is_whitelabeled();
51
- $subscription = ( is_object( $license ) ?
52
- $fs->_get_subscription( $license->id ) :
53
- null );
54
- $plan = $fs->get_plan();
55
- $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() );
56
- $is_paid_trial = $fs->is_paid_trial();
57
- $has_paid_plan = $fs->apply_filters( 'has_paid_plan_account', $fs->has_paid_plan() );
58
- $show_upgrade = ( ! $is_whitelabeled && $has_paid_plan && ! $is_paying && ! $is_paid_trial );
59
- $trial_plan = $fs->get_trial_plan();
60
-
61
- if ( $has_paid_plan ) {
62
- $fs->_add_license_activation_dialog_box();
63
- }
64
-
65
- $ids_of_installs_activated_with_foreign_licenses = $fs->should_handle_user_change() ?
66
- $fs->get_installs_ids_with_foreign_licenses() :
67
- array();
68
-
69
- if ( ! empty( $ids_of_installs_activated_with_foreign_licenses ) ) {
70
- $fs->_add_user_change_dialog_box( $ids_of_installs_activated_with_foreign_licenses );
71
- }
72
-
73
- if ( $fs->is_whitelabeled( true ) || $fs->is_data_debug_mode() ) {
74
- $fs->_add_data_debug_mode_dialog_box();
75
- }
76
-
77
- if ( fs_request_get_bool( 'auto_install' ) ) {
78
- $fs->_add_auto_installation_dialog_box();
79
- }
80
-
81
- if ( fs_request_get_bool( 'activate_license' ) ) {
82
- // Open the license activation dialog box on the account page.
83
- add_action( 'admin_footer', array(
84
- &$fs,
85
- '_open_license_activation_dialog_box'
86
- ) );
87
- }
88
-
89
- $payments = $fs->_fetch_payments();
90
-
91
- $show_billing = ( ! $is_whitelabeled && is_array( $payments ) && 0 < count( $payments ) );
92
-
93
-
94
- $has_tabs = $fs->_add_tabs_before_content();
95
-
96
- if ( $has_tabs ) {
97
- $query_params['tabs'] = 'true';
98
- }
99
-
100
- // Aliases.
101
- $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug );
102
- $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug );
103
- $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug );
104
- /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */
105
- $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 );
106
- $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 );
107
- $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug );
108
- $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 );
109
- $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 );
110
- /* translators: %s: Plan title (e.g. "Professional") */
111
- $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug );
112
- $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug );
113
- /* translators: %s: Time period (e.g. Auto renews in "2 months") */
114
- $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug );
115
- /* translators: %s: Time period (e.g. Expires in "2 months") */
116
- $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug );
117
- $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug );
118
- $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug );
119
- $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug );
120
- $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug );
121
- $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug );
122
- $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug );
123
- $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug );
124
- $free_text = fs_text_inline( 'Free', 'free', $slug );
125
- $activate_text = fs_text_inline( 'Activate', 'activate', $slug );
126
- $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug );
127
- $bundle_plan_text = fs_text_inline( 'Bundle Plan', 'bundle-plan', $slug );
128
-
129
- $show_plan_row = true;
130
- $show_license_row = is_object( $license );
131
-
132
- $site_view_params = array();
133
-
134
- if ( fs_is_network_admin() ) {
135
- $sites = Freemius::get_sites();
136
- $all_installs_plan_id = null;
137
- $all_installs_license_id = ( $show_license_row ? $license->id : null );
138
- foreach ( $sites as $s ) {
139
- $site_info = $fs->get_site_info( $s );
140
- $install = $fs->get_install_by_blog_id( $site_info['blog_id'] );
141
- $view_params = array(
142
- 'freemius' => $fs,
143
- 'license' => $license,
144
- 'site' => $site_info,
145
- 'install' => $install,
146
- );
147
-
148
- $site_view_params[] = $view_params;
149
-
150
- if ( empty( $install ) ) {
151
- continue;
152
- }
153
-
154
- if ( $show_plan_row ) {
155
- if ( is_null( $all_installs_plan_id ) ) {
156
- $all_installs_plan_id = $install->plan_id;
157
- } else if ( $all_installs_plan_id != $install->plan_id ) {
158
- $show_plan_row = false;
159
- }
160
- }
161
-
162
- if ( $show_license_row && $all_installs_license_id != $install->license_id ) {
163
- $show_license_row = false;
164
- }
165
- }
166
- }
167
-
168
- $has_bundle_license = false;
169
-
170
- if ( is_object( $license ) &&
171
- FS_Plugin_License::is_valid_id( $license->parent_license_id )
172
- ) {
173
- // Context license has a parent license, therefore, the account has a bundle license.
174
- $has_bundle_license = true;
175
- }
176
-
177
- $bundle_subscription = null;
178
- $is_bundle_first_payment_pending = false;
179
-
180
- if (
181
- $show_plan_row &&
182
- is_object( $license ) &&
183
- $has_bundle_license
184
- ) {
185
- $bundle_plan_title = strtoupper( $license->parent_plan_title );
186
- $bundle_subscription = $fs->_get_subscription( $license->parent_license_id );
187
- $is_bundle_first_payment_pending = $license->is_first_payment_pending();
188
- }
189
-
190
- $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ?
191
- get_current_blog_id() :
192
- 0;
193
-
194
- $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id );
195
-
196
- $is_premium = $fs->is_premium();
197
-
198
- $account_addons = $fs->get_updated_account_addons();
199
- $installed_addons = $fs->get_installed_addons();
200
- $installed_addons_ids = array();
201
-
202
- /**
203
- * Store the installed add-ons' IDs into a collection which will be used in determining the add-ons to show on the "Account" page, and at the same time try to find an add-on that is activated with a bundle license if the core product is not.
204
- *
205
- * @author Leo Fajardo
206
- *
207
- * @since 2.4.0
208
- */
209
- foreach ( $installed_addons as $fs_addon ) {
210
- $installed_addons_ids[] = $fs_addon->get_id();
211
-
212
- if ( $has_bundle_license ) {
213
- // We already have the context bundle license details, skip.
214
- continue;
215
- }
216
-
217
- if (
218
- $show_plan_row &&
219
- $fs_addon->has_active_valid_license()
220
- ) {
221
- $addon_license = $fs_addon->_get_license();
222
-
223
- if ( FS_Plugin_License::is_valid_id( $addon_license->parent_license_id ) ) {
224
- // Add-on's license is associated with a parent/bundle license.
225
- $has_bundle_license = true;
226
-
227
- $bundle_plan_title = strtoupper( $addon_license->parent_plan_title );
228
- $bundle_subscription = $fs_addon->_get_subscription( $addon_license->parent_license_id );
229
- $is_bundle_first_payment_pending = $addon_license->is_first_payment_pending();
230
- }
231
- }
232
- }
233
-
234
- $addons_to_show = array_unique( array_merge( $installed_addons_ids, $account_addons ) );
235
-
236
- $is_active_bundle_subscription = ( is_object( $bundle_subscription ) && $bundle_subscription->is_active() );
237
- ?>
238
- <div class="wrap fs-section">
239
- <?php if ( ! $has_tabs && ! $fs->apply_filters( 'hide_account_tabs', false ) ) : ?>
240
- <h2 class="nav-tab-wrapper">
241
- <a href="<?php echo $fs->get_account_url() ?>"
242
- class="nav-tab nav-tab-active"><?php fs_esc_html_echo_inline( 'Account', 'account', $slug ) ?></a>
243
- <?php if ( $fs->has_addons() ) : ?>
244
- <a href="<?php echo $fs->_get_admin_page_url( 'addons' ) ?>"
245
- class="nav-tab"><?php echo esc_html( $addons_text ) ?></a>
246
- <?php endif ?>
247
- <?php if ( $show_upgrade ) : ?>
248
- <a href="<?php echo $fs->get_upgrade_url() ?>" class="nav-tab"><?php echo esc_html( $upgrade_text ) ?></a>
249
- <?php if ( $fs->apply_filters( 'show_trial', true ) && ! $fs->is_trial_utilized() && $fs->has_trial_plan() ) : ?>
250
- <a href="<?php echo $fs->get_trial_url() ?>" class="nav-tab"><?php fs_esc_html_echo_inline( 'Free Trial', 'free-trial', $slug ) ?></a>
251
- <?php endif ?>
252
- <?php endif ?>
253
- </h2>
254
- <?php endif ?>
255
-
256
- <div id="poststuff">
257
- <div id="fs_account">
258
- <div class="has-sidebar has-right-sidebar">
259
- <div class="has-sidebar-content">
260
- <div class="postbox">
261
- <h3><span class="dashicons dashicons-businessman"></span> <?php fs_esc_html_echo_inline( 'Account Details', 'account-details', $slug ) ?></h3>
262
- <div class="fs-header-actions">
263
- <ul>
264
- <?php if ( $fs->is_whitelabeled( true ) ) : ?>
265
- <li>
266
- <a href="#" class="debug-license-trigger"><i class="dashicons dashicons-<?php echo $is_whitelabeled ? 'editor-code' : 'controls-pause' ?>"></i> <span><?php
267
- if ( $is_whitelabeled ) {
268
- fs_esc_html_echo_inline( 'Start Debug', 'start-debug-license', $slug );
269
- } else {
270
- fs_esc_html_echo_inline( 'Stop Debug', 'stop-debug-license', $slug );
271
- }
272
- ?></span></a>
273
- </li>
274
- <li>&nbsp;&bull;&nbsp;</li>
275
- <?php endif ?>
276
- <?php if ( $show_billing ) : ?>
277
- <li><a href="#fs_billing"><i class="dashicons dashicons-portfolio"></i> <?php fs_esc_html_echo_inline( 'Billing & Invoices', 'billing-invoices', $slug ) ?></li>
278
- <li>&nbsp;&bull;&nbsp;</li>
279
- <?php endif ?>
280
- <?php if ( ! $is_whitelabeled ) : ?>
281
- <?php if ( ! $is_paying ) : ?>
282
- <li>
283
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
284
- <input type="hidden" name="fs_action" value="delete_account">
285
- <?php wp_nonce_field( 'delete_account' ) ?>
286
- <a class="fs-delete-account" href="#" onclick="if (confirm('<?php
287
- if ( $is_active_subscription ) {
288
- echo esc_attr( sprintf( fs_text_inline( '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?', 'delete-account-x-confirm', $slug ), $plan->title ) );
289
- } else {
290
- echo esc_attr( sprintf( fs_text_inline( '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?', 'delete-account-confirm', $slug ), $fs->get_module_label( true ) ) );
291
- }
292
- ?>')) this.parentNode.submit(); return false;"><i
293
- class="dashicons dashicons-no"></i> <?php fs_esc_html_echo_inline( 'Delete Account', 'delete-account', $slug ) ?></a>
294
- </form>
295
- </li>
296
- <li>&nbsp;&bull;&nbsp;</li>
297
- <?php endif ?>
298
- <?php if ( $is_paying ) : ?>
299
- <?php if ( ! fs_is_network_admin() ) : ?>
300
- <li>
301
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
302
- <input type="hidden" name="fs_action" value="deactivate_license">
303
- <?php wp_nonce_field( 'deactivate_license' ) ?>
304
- <a href="#" class="fs-deactivate-license"><i
305
- class="dashicons dashicons-admin-network"></i> <?php fs_echo_inline( 'Deactivate License', 'deactivate-license', $slug ) ?>
306
- </a>
307
- </form>
308
- </li>
309
- <li>&nbsp;&bull;&nbsp;</li>
310
- <?php endif ?>
311
- <?php if ( ! $license->is_lifetime() &&
312
- $is_active_subscription
313
- ) : ?>
314
- <li>
315
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
316
- <input type="hidden" name="fs_action" value="downgrade_account">
317
- <?php wp_nonce_field( 'downgrade_account' ) ?>
318
- <a href="#"
319
- onclick="if ( confirm('<?php echo esc_attr( sprintf(
320
- $downgrade_x_confirm_text,
321
- ( $fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ),
322
- $plan->title,
323
- human_time_diff( time(), strtotime( $license->expiration ) )
324
- ) ) ?> <?php if ( ! $license->is_block_features ) {
325
- echo esc_attr( sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) );
326
- } else {
327
- echo esc_attr( sprintf( $after_downgrade_blocking_text, $plan->title ) );
328
- }?> <?php echo esc_attr( $prices_increase_text ) ?> <?php fs_esc_attr_echo_inline( 'Are you sure you want to proceed?', 'proceed-confirmation', $slug ) ?>') ) this.parentNode.submit(); return false;"><i class="dashicons dashicons-download"></i> <?php echo esc_html( $fs->is_only_premium() ? fs_text_inline( 'Cancel Subscription', 'cancel-subscription', $slug ) : $downgrade_text ) ?></a>
329
- </form>
330
- </li>
331
- <li>&nbsp;&bull;&nbsp;</li>
332
- <?php endif ?>
333
- <?php if ( ! $fs->is_single_plan() ) : ?>
334
- <li>
335
- <a href="<?php echo $fs->get_upgrade_url() ?>"><i
336
- class="dashicons dashicons-grid-view"></i> <?php echo esc_html( $change_plan_text ) ?></a>
337
- </li>
338
- <li>&nbsp;&bull;&nbsp;</li>
339
- <?php endif ?>
340
- <?php elseif ( $is_paid_trial ) : ?>
341
- <li>
342
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
343
- <input type="hidden" name="fs_action" value="cancel_trial">
344
- <?php wp_nonce_field( 'cancel_trial' ) ?>
345
- <a href="#" class="fs-cancel-trial"><i
346
- class="dashicons dashicons-download"></i> <?php echo esc_html( $cancel_trial_text ) ?></a>
347
- </form>
348
- </li>
349
- <li>&nbsp;&bull;&nbsp;</li>
350
- <?php endif ?>
351
- <?php endif ?>
352
- <li>
353
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
354
- <input type="hidden" name="fs_action" value="<?php echo $fs->get_unique_affix() ?>_sync_license">
355
- <?php wp_nonce_field( $fs->get_unique_affix() . '_sync_license' ) ?>
356
- <a href="#" onclick="this.parentNode.submit(); return false;"><i
357
- class="dashicons dashicons-image-rotate"></i> <?php fs_esc_html_echo_x_inline( 'Sync', 'as synchronize', 'sync', $slug ) ?></a>
358
- </form>
359
- </li>
360
- </ul>
361
- </div>
362
- <div class="inside">
363
- <table id="fs_account_details" cellspacing="0" class="fs-key-value-table">
364
- <?php
365
- $hide_license_key = ( ! $show_license_row || $fs->apply_filters( 'hide_license_key', false ) );
366
-
367
- $profile = array();
368
-
369
- if ( ! $is_whitelabeled ) {
370
- $profile[] = array(
371
- 'id' => 'user_name',
372
- 'title' => fs_text_inline( 'Name', 'name', $slug ),
373
- 'value' => $name
374
- );
375
- // if (isset($user->email) && false !== strpos($user->email, '@'))
376
- $profile[] = array(
377
- 'id' => 'email',
378
- 'title' => fs_text_inline( 'Email', 'email', $slug ),
379
- 'value' => $user->email
380
- );
381
-
382
- if ( is_numeric( $user->id ) ) {
383
- $profile[] = array(
384
- 'id' => 'user_id',
385
- 'title' => fs_text_inline( 'User ID', 'user-id', $slug ),
386
- 'value' => $user->id
387
- );
388
- }
389
- }
390
-
391
- $profile[] = array(
392
- 'id' => 'product',
393
- 'title' => ( $fs->is_plugin() ?
394
- fs_text_inline( 'Plugin', 'plugin', $slug ) :
395
- fs_text_inline( 'Theme', 'theme', $slug ) ),
396
- 'value' => $fs->get_plugin_title()
397
- );
398
-
399
- $profile[] = array(
400
- 'id' => 'product_id',
401
- 'title' => ( $fs->is_plugin() ?
402
- fs_text_inline( 'Plugin', 'plugin', $slug ) :
403
- fs_text_inline( 'Theme', 'theme', $slug ) ) . ' ' . fs_text_inline( 'ID', 'id', $slug ),
404
- 'value' => $fs->get_id()
405
- );
406
-
407
- if ( ! fs_is_network_admin()) {
408
- $profile[] = array(
409
- 'id' => 'site_id',
410
- 'title' => fs_text_inline( 'Site ID', 'site-id', $slug ),
411
- 'value' => is_string( $site->id ) ?
412
- $site->id :
413
- fs_text_inline( 'No ID', 'no-id', $slug )
414
- );
415
-
416
- $profile[] = array(
417
- 'id' => 'site_public_key',
418
- 'title' => fs_text_inline( 'Public Key', 'public-key', $slug ),
419
- 'value' => $site->public_key
420
- );
421
-
422
- $profile[] = array(
423
- 'id' => 'site_secret_key',
424
- 'title' => fs_text_inline( 'Secret Key', 'secret-key', $slug ),
425
- 'value' => ( ( is_string( $site->secret_key ) ) ?
426
- $site->secret_key :
427
- fs_text_x_inline( 'No Secret', 'as secret encryption key missing', 'no-secret', $slug )
428
- )
429
- );
430
- }
431
-
432
- $profile[] = array(
433
- 'id' => 'version',
434
- 'title' => $version_text,
435
- 'value' => $fs->get_plugin_version()
436
- );
437
-
438
- if ( ! fs_is_network_admin() && $is_premium && ! $is_whitelabeled ) {
439
- $profile[] = array(
440
- 'id' => 'beta_program',
441
- 'title' => '',
442
- 'value' => $site->is_beta
443
- );
444
- }
445
-
446
- if ( $has_paid_plan || $has_bundle_license ) {
447
- if ( $fs->is_trial() ) {
448
- if ( $show_plan_row ) {
449
- $profile[] = array(
450
- 'id' => 'plan',
451
- 'title' => $plan_text,
452
- 'value' => ( is_string( $trial_plan->name ) ?
453
- strtoupper( $trial_plan->title ) :
454
- fs_text_inline( 'Trial', 'trial', $slug ) )
455
- );
456
- }
457
- } else {
458
- if ( $show_plan_row ) {
459
- $profile[] = array(
460
- 'id' => 'plan',
461
- 'title' => ( $has_bundle_license ? ucfirst( $fs->get_module_type() ) . ' ' : '' ) . $plan_text,
462
- 'value' => strtoupper( is_string( $plan->name ) ?
463
- $plan->title :
464
- strtoupper( $free_text )
465
- )
466
- );
467
-
468
- if ( $has_bundle_license ) {
469
- $profile[] = array(
470
- 'id' => 'bundle_plan',
471
- 'title' => $bundle_plan_text,
472
- 'value' => $bundle_plan_title
473
- );
474
- }
475
- }
476
-
477
- if ( is_object( $license ) ) {
478
- if ( ! $hide_license_key ) {
479
- $profile[] = array(
480
- 'id' => 'license_key',
481
- 'title' => fs_text_inline( 'License Key', $slug ),
482
- 'value' => $license->secret_key,
483
- );
484
- }
485
- }
486
- }
487
- }
488
- ?>
489
- <?php $odd = true;
490
- foreach ( $profile as $p ) : ?>
491
- <?php
492
- if ( 'plan' === $p['id'] && ! $has_paid_plan ) {
493
- // If plugin don't have any paid plans, there's no reason
494
- // to show current plan.
495
- continue;
496
- }
497
- ?>
498
- <tr class="fs-field-<?php echo $p['id'] ?><?php if ( $odd ) : ?> alternate<?php endif ?>">
499
- <td>
500
- <nobr><?php echo $p['title'] ?><?php echo ( ! empty( $p['title'] ) ) ? ':' : '' ?></nobr>
501
- </td>
502
- <td<?php if ( 'plan' === $p['id'] || 'bundle_plan' === $p['id'] ) { echo ' colspan="2"'; }?>>
503
- <?php if ( in_array( $p['id'], array( 'license_key', 'site_secret_key' ) ) ) : ?>
504
- <code><?php echo FS_Plugin_License::mask_secret_key_for_html( $p['value'] ) ?></code>
505
- <?php if ( ! $is_whitelabeled ) : ?>
506
- <input type="text" value="<?php echo htmlspecialchars( $p['value'] ) ?>" style="display: none"
507
- readonly/>
508
- <?php endif ?>
509
- <?php elseif ( 'beta_program' === $p['id'] ) : ?>
510
- <label>
511
- <input type="checkbox" class="fs-toggle-beta-mode" <?php checked( true, $p['value'] ) ?>/><span><?php
512
- fs_esc_html_echo_inline( 'Join the Beta program', 'join-beta', $slug )
513
- ?></span></label>
514
- <?php else : ?>
515
- <code><?php echo htmlspecialchars( $p['value'] ) ?></code>
516
- <?php endif ?>
517
- <?php if ( 'email' === $p['id'] && ! $user->is_verified() ) : ?>
518
- <label class="fs-tag fs-warn"><?php fs_esc_html_echo_inline( 'not verified', 'not-verified', $slug ) ?></label>
519
- <?php endif ?>
520
- <?php if ( 'plan' === $p['id'] ) : ?>
521
- <?php if ( $fs->is_trial() ) : ?>
522
- <label class="fs-tag fs-success"><?php echo esc_html( $trial_text ) ?></label>
523
- <?php endif ?>
524
- <?php if ( is_object( $license ) && ! $license->is_lifetime() ) : ?>
525
- <?php if ( ! $is_active_subscription && ! $is_active_bundle_subscription && ! $license->is_first_payment_pending() ) : ?>
526
- <?php $is_license_expired = $license->is_expired() ?>
527
- <?php $expired_ago_text = ( fs_text_inline( 'Expired', 'expired', $slug ) . ' ' . fs_text_x_inline( '%s ago', 'x-ago', $slug ) ) ?>
528
- <label
529
- class="fs-tag <?php echo $is_license_expired ? 'fs-error' : 'fs-warn' ?>"><?php
530
- echo esc_html( sprintf( $is_license_expired ? $expired_ago_text : $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ) )
531
- ?></label>
532
- <?php elseif ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) : ?>
533
- <label class="fs-tag fs-success"><?php echo esc_html( sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ) ) ?></label>
534
- <?php endif ?>
535
- <?php elseif ( $fs->is_trial() ) : ?>
536
- <label class="fs-tag fs-warn"><?php echo esc_html( sprintf( $expires_in_text, human_time_diff( time(), strtotime( $site->trial_ends ) ) ) ) ?></label>
537
- <?php endif ?>
538
- <?php if ( ! $is_whitelabeled ) : ?>
539
- <div class="button-group">
540
- <?php $available_license = $fs->is_free_plan() && ! fs_is_network_admin() ? $fs->_get_available_premium_license( $site->is_localhost() ) : false ?>
541
- <?php if ( is_object( $available_license ) ) : ?>
542
- <?php $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id ) ?>
543
- <?php
544
- $view_params = array(
545
- 'freemius' => $fs,
546
- 'slug' => $slug,
547
- 'license' => $available_license,
548
- 'plan' => $premium_plan,
549
- 'is_localhost' => $site->is_localhost(),
550
- 'install_id' => $site->id,
551
- 'class' => 'button-primary',
552
- );
553
- fs_require_template( 'account/partials/activate-license-button.php', $view_params ); ?>
554
- <?php else : ?>
555
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>"
556
- method="POST" class="button-group">
557
- <?php if ( $show_upgrade && $is_premium ) : ?>
558
- <a class="button activate-license-trigger <?php echo $fs->get_unique_affix() ?>" href="#"><?php fs_esc_html_echo_inline( 'Activate License', 'activate-license', $slug ) ?></a>
559
- <?php endif ?>
560
- <input type="submit" class="button"
561
- value="<?php echo esc_attr( $sync_license_text ) ?>">
562
- <input type="hidden" name="fs_action"
563
- value="<?php echo $fs->get_unique_affix() ?>_sync_license">
564
- <?php wp_nonce_field( $fs->get_unique_affix() . '_sync_license' ) ?>
565
- <?php if ( $show_upgrade || ! $fs->is_single_plan() ) : ?>
566
- <a href="<?php echo $fs->get_upgrade_url() ?>"
567
- class="button<?php
568
- echo $show_upgrade ?
569
- ' button-primary fs-upgrade' :
570
- ' fs-change-plan'; ?> button-upgrade"><i
571
- class="dashicons dashicons-cart"></i> <?php echo esc_html( $show_upgrade ? $upgrade_text : $change_plan_text ) ?></a>
572
- <?php endif ?>
573
- </form>
574
- <?php endif ?>
575
- </div>
576
- <?php endif ?>
577
- <?php elseif ( 'bundle_plan' === $p['id'] ) : ?>
578
- <?php if ( is_object( $bundle_subscription ) ) : ?>
579
- <?php if ( $is_active_bundle_subscription && ! $is_bundle_first_payment_pending ) : ?>
580
- <label class="fs-tag fs-success"><?php echo esc_html( sprintf( $renews_in_text, human_time_diff( time(), strtotime( $bundle_subscription->next_payment ) ) ) ) ?></label>
581
- <?php endif ?>
582
- <?php endif ?>
583
- <?php elseif ( 'version' === $p['id'] && $has_paid_plan ) : ?>
584
- <?php if ( $fs->has_premium_version() ) : ?>
585
- <?php if ( $is_premium ) : ?>
586
- <label
587
- class="fs-tag fs-<?php echo $fs->can_use_premium_code() ? 'success' : 'warn' ?>"><?php fs_esc_html_echo_inline( 'Premium version', 'premium-version', $slug ) ?></label>
588
- <?php elseif ( $fs->can_use_premium_code() ) : ?>
589
- <label class="fs-tag fs-warn"><?php fs_esc_html_echo_inline( 'Free version', 'free-version', $slug ) ?></label>
590
- <?php endif ?>
591
- <?php endif ?>
592
- <?php endif ?>
593
- </td>
594
- <?php if ( 'plan' !== $p['id'] && 'bundle_plan' !== $p['id'] ) : ?>
595
- <td class="fs-right">
596
- <?php if ( 'email' === $p['id'] && ! $user->is_verified() ) : ?>
597
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
598
- <input type="hidden" name="fs_action" value="verify_email">
599
- <?php wp_nonce_field( 'verify_email' ) ?>
600
- <input type="submit" class="button button-small"
601
- value="<?php fs_esc_attr_echo_inline( 'Verify Email', 'verify-email', $slug ) ?>">
602
- </form>
603
- <?php endif ?>
604
- <?php if ( 'version' === $p['id'] ) : ?>
605
- <?php if ( $fs->has_release_on_freemius() ) : ?>
606
- <div class="button-group">
607
- <?php if ( $is_paying || $fs->is_trial() ) : ?>
608
- <?php if ( ! $fs->is_allowed_to_install() ) : ?>
609
- <a target="_blank" rel="noopener" class="button button-primary"
610
- href="<?php echo $fs->_get_latest_download_local_url() ?>"><?php
611
- $download_version_text_suffix = ( is_object( $update ) ? ' [' . $update->version . ']' : '' );
612
-
613
- $download_version_text = sprintf(
614
- /* translators: %s: plan name (e.g. Download "Professional" Version) */
615
- fs_text_inline( 'Download %s Version', 'download-x-version', $slug ),
616
- ( $fs->is_trial() ? $trial_plan->title : $plan->title )
617
- ) .
618
- $download_version_text_suffix;
619
-
620
- $download_version_text_length = function_exists( 'mb_strlen' ) ?
621
- mb_strlen( $download_version_text ) :
622
- strlen( $download_version_text );
623
-
624
- if ( $download_version_text_length > 31 ) {
625
- /**
626
- * Try to limit the number of characters to 31 for now.
627
- *
628
- * @author Leo Fajardo (@leorw)
629
- * @aince 2.3.2
630
- */
631
- $download_version_text = fs_text_inline( 'Download Paid Version', 'download-paid-version', $slug ) . $download_version_text_suffix;
632
- }
633
-
634
- echo $download_version_text;
635
- ?></a>
636
- <?php elseif ( is_object( $update ) ) : ?>
637
- <?php
638
- $module_type = $fs->get_module_type();
639
- ?>
640
- <a class="button button-primary"
641
- href="<?php echo wp_nonce_url( self_admin_url( "update.php?action=upgrade-{$module_type}&{$module_type}=" . $fs->get_plugin_basename() ), "upgrade-{$module_type}_" . $fs->get_plugin_basename() ) ?>"><?php echo fs_esc_html_inline( 'Install Update Now', 'install-update-now', $slug ) . ' [' . $update->version . ']' ?></a>
642
- <?php endif ?>
643
- <?php endif; ?>
644
- </div>
645
- <?php endif ?>
646
- <?php
647
- elseif ( in_array( $p['id'], array( 'license_key', 'site_secret_key' ) ) ) : ?>
648
- <?php if ( ! $is_whitelabeled ) : ?>
649
- <button class="button button-small fs-toggle-visibility"><?php fs_esc_html_echo_x_inline( 'Show', 'verb', 'show', $slug ) ?></button>
650
- <?php endif ?>
651
- <?php if ('license_key' === $p['id']) : ?>
652
- <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>
653
- <?php endif ?>
654
- <?php
655
- elseif (/*in_array($p['id'], array('site_secret_key', 'site_id', 'site_public_key')) ||*/
656
- ( is_string( $user->secret_key ) && in_array( $p['id'], array(
657
- 'email',
658
- 'user_name'
659
- ) ) )
660
- ) : ?>
661
- <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST"
662
- onsubmit="var val = prompt('<?php echo esc_attr( sprintf(
663
- /* translators: %s: User's account property (e.g. name, email) */
664
- fs_text_inline( 'What is your %s?', 'what-is-your-x', $slug ),
665
- $p['title']
666
- ) ) ?>', '<?php echo $p['value'] ?>'); if (null == val || '' === val) return false; jQuery('input[name=fs_<?php echo $p['id'] ?>_<?php echo $fs->get_unique_affix() ?>]').val(val); return true;">
667
- <input type="hidden" name="fs_action" value="update_<?php echo $p['id'] ?>">
668
- <input type="hidden" name="fs_<?php echo $p['id'] ?>_<?php echo $fs->get_unique_affix() ?>"
669
- value="">
670
- <?php wp_nonce_field( 'update_' . $p['id'] ) ?>
671
- <input type="submit" class="button button-small"
672
- value="<?php echo fs_esc_attr_x_inline( 'Edit', 'verb', 'edit', $slug ) ?>">
673
- </form>
674
- <?php elseif ( 'user_id' === $p['id'] && ! empty( $ids_of_installs_activated_with_foreign_licenses ) ) : ?>
675
- <input id="fs_change_user" type="submit" class="button button-small"
676
- value="<?php echo fs_esc_attr_inline( 'Change User', 'change-user', $slug ) ?>">
677
- <?php endif ?>
678
- </td>
679
- <?php endif ?>
680
- </tr>
681
- <?php
682
- if ( 'version' === $p['id'] && $is_premium ) {
683
- /**
684
- * If there's a row for the beta program, keep its background color
685
- * the same as the version info row.
686
- *
687
- * @author Leo Fajardo (@leorw)
688
- * @since 2.3.0
689
- */
690
- continue;
691
- }
692
-
693
- $odd = ! $odd;
694
- endforeach ?>
695
- </table>
696
- </div>
697
- </div>
698
- <?php if ( fs_is_network_admin() ) : ?>
699
- <div id="fs_sites" class="postbox">
700
- <h3><span class="dashicons dashicons-networking"></span> <?php fs_esc_html_echo_inline( 'Sites', 'sites', $slug ) ?></h3>
701
- <div class="fs-header-actions">
702
- <?php if ( ! $is_whitelabeled ) : ?>
703
- <?php $has_license = is_object( $license ) ?>
704
- <?php if ( $has_license || ( $show_upgrade && $is_premium ) ) : ?>
705
- <?php
706
- $activate_license_button_text = $has_license ?
707
- fs_esc_html_inline( 'Change License', 'change-license', $slug ) :
708
- fs_esc_html_inline( 'Activate License', 'activate-license', $slug );
709
- ?>
710
- <a class="button<?php echo ( ! $has_license ? ' button-primary' : '' ) ?> activate-license-trigger <?php echo $fs->get_unique_affix() ?>" href="#"><?php echo $activate_license_button_text ?></a>
711
- <?php endif ?>
712
- <?php endif ?>
713
- <input class="fs-search" type="text" placeholder="<?php fs_esc_attr_echo_inline( 'Search by address', 'search-by-address', $slug ) ?>..."><span class="dashicons dashicons-search"></span>
714
- </div>
715
- <div class="inside">
716
- <div id="" class="fs-scrollable-table">
717
- <div class="fs-table-head">
718
- <table class="widefat">
719
- <thead>
720
- <tr>
721
- <td><?php fs_esc_html_echo_inline('ID', 'id', $slug) ?></td>
722
- <td><?php fs_esc_html_echo_inline('Address', 'address', $slug) ?></td>
723
- <td><?php fs_esc_html_echo_inline('License', 'license', $slug) ?></td>
724
- <td><?php fs_esc_html_echo_inline('Plan', 'plan', $slug) ?></td>
725
- <td></td>
726
- </tr>
727
- </thead>
728
- </table>
729
- </div>
730
- <div class="fs-table-body">
731
- <table class="widefat">
732
- <?php
733
- foreach ( $site_view_params as $view_params ) {
734
- fs_require_template(
735
- 'account/partials/site.php',
736
- $view_params
737
- );
738
- } ?>
739
- </table>
740
- </div>
741
- </div>
742
- </div>
743
- </div>
744
- <?php endif ?>
745
- <?php if ( 0 < count( $addons_to_show ) ) : ?>
746
- <!-- Add-Ons -->
747
- <div class="postbox">
748
- <div class="">
749
- <!-- <div class="inside">-->
750
- <table id="fs_addons" class="widefat">
751
- <thead>
752
- <tr>
753
- <th><h3><?php echo esc_html( $addons_text ) ?></h3></th>
754
- <th><?php fs_esc_html_echo_inline( 'ID', 'id', $slug ) ?></th>
755
- <th><?php echo esc_html( $version_text ) ?></th>
756
- <th><?php echo esc_html( $plan_text ) ?></th>
757
- <th><?php fs_esc_html_echo_x_inline( 'License', 'as software license', 'license', $slug ) ?></th>
758
- <th></th>
759
- <?php if ( defined( 'WP_FS__DEV_MODE' ) && WP_FS__DEV_MODE ) : ?>
760
- <th></th>
761
- <?php endif ?>
762
- </tr>
763
- </thead>
764
- <tbody>
765
- <?php
766
- $odd = true;
767
-
768
- $installed_addons_ids_map = array_flip( $installed_addons_ids );
769
-
770
- $addon_info_by_id = array();
771
- $hide_all_addons_data = false;
772
-
773
- if ( $fs->is_whitelabeled_by_flag() ) {
774
- $hide_all_addons_data = true;
775
-
776
- foreach ( $addons_to_show as $addon_id ) {
777
- $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] );
778
- $addon_info = $fs->_get_addon_info( $addon_id, $is_addon_installed );
779
- $is_addon_connected = $addon_info['is_connected'];
780
-
781
- $fs_addon = ( $is_addon_connected && $is_addon_installed ) ?
782
- freemius( $addon_id ) :
783
- null;
784
-
785
- $is_whitelabeled = is_object( $fs_addon ) ?
786
- $fs_addon->is_whitelabeled( true ) :
787
- $addon_info['is_whitelabeled'];
788
-
789
- if ( ! $is_whitelabeled ) {
790
- $hide_all_addons_data = false;
791
- }
792
-
793
- if ( $is_data_debug_mode ) {
794
- $is_whitelabeled = false;
795
- }
796
-
797
- $addon_info_by_id[ $addon_id ] = $addon_info;
798
- }
799
- }
800
-
801
- foreach ( $addons_to_show as $addon_id ) {
802
- $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] );
803
-
804
- if (
805
- $hide_all_addons_data &&
806
- ! $is_addon_installed &&
807
- ! file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $fs->get_addon_basename( $addon_id ) ) )
808
- ) {
809
- continue;
810
- }
811
-
812
- $addon_view_params = array(
813
- 'parent_fs' => $fs,
814
- 'addon_id' => $addon_id,
815
- 'odd' => $odd,
816
- 'fs_blog_id' => $fs_blog_id,
817
- 'active_plugins_directories_map' => &$active_plugins_directories_map,
818
- 'is_addon_installed' => $is_addon_installed,
819
- 'addon_info' => isset( $addon_info_by_id[ $addon_id ] ) ?
820
- $addon_info_by_id[ $addon_id ] :
821
- $fs->_get_addon_info( $addon_id, $is_addon_installed ),
822
- 'is_whitelabeled' => ( $is_whitelabeled && ! $is_data_debug_mode )
823
- );
824
-
825
- fs_require_template(
826
- 'account/partials/addon.php',
827
- $addon_view_params
828
- );
829
-
830
- $odd = ! $odd;
831
- } ?>
832
- </tbody>
833
- </table>
834
- </div>
835
- </div>
836
- <?php endif ?>
837
-
838
- <?php $fs->do_action( 'after_account_details' ) ?>
839
-
840
- <?php
841
- if ( $show_billing ) {
842
- $view_params = array( 'id' => $VARS['id'] );
843
- fs_require_once_template( 'account/billing.php', $view_params );
844
- fs_require_once_template( 'account/payments.php', $view_params );
845
- }
846
- ?>
847
- </div>
848
- </div>
849
- </div>
850
- </div>
851
- </div>
852
- <?php
853
- $subscription_cancellation_dialog_box_template_params = $fs->_get_subscription_cancellation_dialog_box_template_params( true );
854
- if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) {
855
- fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params );
856
- }
857
- ?>
858
- <script type="text/javascript">
859
- (function ($) {
860
- var setLoading = function ($this, label) {
861
- // Set loading mode.
862
- $(document.body).css({'cursor': 'wait'});
863
-
864
- $this.css({'cursor': 'wait'});
865
-
866
- if ($this.is('input'))
867
- $this.val(label);
868
- else
869
- $this.html(label);
870
-
871
- setTimeout(function () {
872
- $this.attr('disabled', 'disabled');
873
- }, 200);
874
- };
875
-
876
- $('.fs-toggle-visibility').click(function () {
877
- var
878
- $this = $(this),
879
- $parent = $this.closest('tr'),
880
- $input = $parent.find('input');
881
-
882
- $parent.find('code').toggle();
883
- $input.toggle();
884
-
885
- if ($input.is(':visible')) {
886
- $this.html('<?php fs_esc_js_echo_x_inline( 'Hide', 'verb', 'hide', $slug ) ?>');
887
- setTimeout(function () {
888
- $input.select().focus();
889
- }, 100);
890
- }
891
- else {
892
- $this.html( '<?php fs_esc_js_echo_x_inline( 'Show', 'verb', 'show', $slug ) ?>' );
893
- }
894
- });
895
-
896
- $('.fs-toggle-tracking').click(function () {
897
- setLoading(
898
- $(this),
899
- ($(this).data('is-disconnected') ?
900
- '<?php fs_esc_js_echo_inline('Opting in', 'opting-in' ) ?>' :
901
- '<?php fs_esc_js_echo_inline('Opting out', 'opting-out' ) ?>') +
902
- '...'
903
- );
904
- });
905
-
906
- <?php
907
- $plugin_title = $fs->get_plugin_title();
908
- $processing_text = fs_esc_js_inline( 'Processing', 'processing' );
909
- $confirmation_message = sprintf(
910
- '%s %s',
911
- sprintf( fs_esc_attr_inline( 'Get updates for bleeding edge Beta versions of %s.', 'get-beta-versions', $slug ), $plugin_title ),
912
- sprintf( fs_esc_attr_inline( 'An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.', 'beta-version-update-caution', $slug ), $plugin_title )
913
- );
914
- ?>
915
-
916
- $( '.fs-toggle-beta-mode' ).click( function () {
917
- var $checkbox = $( this ),
918
- isChecked = $checkbox.is( ':checked' );
919
-
920
- if ( ! isChecked || confirm( '<?php echo $confirmation_message ?>' ) ) {
921
- $.ajax( {
922
- url : ajaxurl,
923
- method: 'POST',
924
- data : {
925
- action : '<?php echo $fs->get_ajax_action( 'set_beta_mode' ) ?>',
926
- security : '<?php echo $fs->get_ajax_security( 'set_beta_mode' ) ?>',
927
- is_beta : isChecked,
928
- module_id: <?php echo $fs->get_id() ?>
929
- },
930
- beforeSend: function () {
931
- $checkbox.prop( 'disabled', true );
932
- $checkbox.parent().find( 'span' ).text( '<?php echo $processing_text ?>' + '...' );
933
- },
934
- complete: function () {
935
- $checkbox.prop( 'disabled', false );
936
- $checkbox.parent().find( 'span' ).text( '<?php fs_esc_js_echo_inline( 'Join the Beta Program', 'join-beta', $slug ) ?>' );
937
- }
938
- } );
939
-
940
- return true;
941
- }
942
-
943
- return false;
944
- });
945
-
946
- $('.fs-opt-in').click(function () {
947
- setLoading($(this), '<?php fs_esc_js_echo_inline('Opting in', 'opting-in' ) ?>...');
948
- });
949
-
950
- $( '#fs_downgrade' ).submit(function( event ) {
951
- event.preventDefault();
952
-
953
- setLoading( $( this ).find( '.button' ), '<?php fs_esc_js_echo_inline( 'Downgrading', 'downgrading' ) ?>...' );
954
- });
955
-
956
- $('.fs-activate-license').click(function () {
957
- setLoading($(this), '<?php fs_esc_js_echo_inline('Activating', 'activating' ) ?>...');
958
- });
959
-
960
- var $deactivateLicenseOrCancelTrial = $( '.fs-deactivate-license, .fs-cancel-trial' ),
961
- $subscriptionCancellationModal = $( '.fs-modal-subscription-cancellation-<?php echo $fs->get_id() ?>' );
962
-
963
- if ( 0 !== $subscriptionCancellationModal.length ) {
964
- $subscriptionCancellationModal.on( '<?php echo $fs->get_action_tag( 'subscription_cancellation_action' ) ?>', function( evt, cancelSubscription ) {
965
- setLoading(
966
- $deactivateLicenseOrCancelTrial,
967
- ( ! $deactivateLicenseOrCancelTrial.hasClass( 'fs-cancel-trial' ) ?
968
- '<?php fs_esc_js_echo_inline( 'Deactivating', 'deactivating', $slug ) ?>' :
969
- '<?php echo esc_html( sprintf( fs_text_inline( 'Cancelling %s', 'cancelling-x', $slug ), fs_text_inline( 'trial', 'trial', $slug ) ) ) ?>' ) + '...'
970
- );
971
-
972
- $subscriptionCancellationModal.find( '.fs-modal-footer .button' ).addClass( 'disabled' );
973
- $deactivateLicenseOrCancelTrial.unbind( 'click' );
974
-
975
- if ( false === cancelSubscription || $deactivateLicenseOrCancelTrial.hasClass( 'fs-cancel-trial' ) ) {
976
- $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).text( $deactivateLicenseOrCancelTrial.text() );
977
-
978
- $deactivateLicenseOrCancelTrial[0].parentNode.submit();
979
- } else {
980
- var $form = $( 'input[value="downgrade_account"],input[value="cancel_trial"]' ).parent();
981
- $form.prepend( '<input type="hidden" name="deactivate_license" value="true" />' );
982
-
983
- $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).text( '<?php echo esc_js( sprintf(
984
- fs_text_inline( 'Cancelling %s...', 'cancelling-x' , $slug ),
985
- $is_paid_trial ?
986
- fs_text_inline( 'trial', 'trial', $slug ) :
987
- fs_text_inline( 'subscription', 'subscription', $slug )
988
- ) ) ?>' );
989
-
990
- $form.submit();
991
- }
992
- });
993
- }
994
-
995
- $deactivateLicenseOrCancelTrial.click(function() {
996
- var $this = $( this );
997
- if ( $this.hasClass( 'fs-cancel-trial' ) ) {
998
- $subscriptionCancellationModal.find( '.fs-modal-panel' ).find( 'ul.subscription-actions, .fs-price-increase-warning' ).remove();
999
- $subscriptionCancellationModal.find( '.fs-modal-panel > p' ).text( <?php echo json_encode( $cancel_trial_confirm_text ) ?> );
1000
- $subscriptionCancellationModal.trigger( 'showModal' );
1001
- } else if (confirm('<?php fs_esc_attr_echo_inline( 'Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?', 'deactivate-license-confirm', $slug ) ?>')) {
1002
- var $this = $(this);
1003
-
1004
- if ( 0 !== $subscriptionCancellationModal.length ) {
1005
- $subscriptionCancellationModal.trigger( 'showModal' );
1006
- } else {
1007
- setLoading( $this, '<?php fs_esc_js_echo_inline( 'Deactivating', 'deactivating', $slug ) ?>...' );
1008
- $this[0].parentNode.submit();
1009
- }
1010
- }
1011
-
1012
- return false;
1013
- });
1014
-
1015
- var $sitesSection = $('#fs_sites'),
1016
- $sitesTable = $sitesSection.find('.fs-scrollable-table'),
1017
- $sitesTableRows = $sitesTable.find('.fs-site-details');
1018
-
1019
- $('.fs-show-install-details').click(function(){
1020
- var installID = $(this).parents('.fs-site-details').attr('data-install-id');
1021
- $sitesSection.find('.fs-install-details[data-install-id=' + installID + ']').toggle();
1022
- });
1023
-
1024
-
1025
- var adjustColumnWidth = function($table) {
1026
- var $headerColumns = $table.find('.fs-table-head td'),
1027
- $bodyColumns = $table.find('.fs-table-body tr:first > td');
1028
-
1029
- for (var i = 0, len = $headerColumns.length; i < len; i++) {
1030
- $($headerColumns[i]).width($($bodyColumns[i]).width());
1031
- }
1032
- for (i = 0, len = $headerColumns.length; i < len; i++) {
1033
- $($bodyColumns[i]).width($($headerColumns[i]).width());
1034
- }
1035
- };
1036
-
1037
- adjustColumnWidth($sitesTable);
1038
-
1039
- $sitesSection.find('.fs-search').keyup(function(){
1040
- var search = $(this).val().trim();
1041
-
1042
- if ('' === search){
1043
- // Show all.
1044
- $sitesTableRows.show();
1045
- return;
1046
- }
1047
-
1048
- var url;
1049
-
1050
- $sitesTableRows.each(function(index){
1051
- url = $(this).find('.fs-field-url').html();
1052
-
1053
- if (-1 < url.indexOf(search)){
1054
- $(this).show();
1055
- } else {
1056
- $(this).hide();
1057
- }
1058
- });
1059
- });
1060
-
1061
- $( '.fs-toggle-whitelabel-mode' ).click( function () {
1062
- var $toggleLink = $( this );
1063
-
1064
- $.ajax( {
1065
- url : ajaxurl,
1066
- method: 'POST',
1067
- data : {
1068
- action : '<?php echo $fs->get_ajax_action( 'toggle_whitelabel_mode' ) ?>',
1069
- security : '<?php echo $fs->get_ajax_security( 'toggle_whitelabel_mode' ) ?>',
1070
- module_id: <?php echo $fs->get_id() ?>
1071
- },
1072
- beforeSend: function () {
1073
- $toggleLink.parent().text( '<?php
1074
- $is_whitelabeled ?
1075
- fs_esc_html_echo_inline( 'Disabling white-label mode', 'disabling-whitelabel-mode' ) :
1076
- fs_esc_html_echo_inline( 'Enabling white-label mode', 'enabling-whitelabel-mode' )
1077
- ?>' + '...' );
1078
- },
1079
- complete: function () {
1080
- location.reload();
1081
- }
1082
- } );
1083
- });
1084
- })(jQuery);
1085
- </script>
1086
- <?php
1087
- if ( $has_tabs ) {
1088
- $fs->_add_tabs_after_content();
1089
- }
1090
-
1091
- $params = array(
1092
- 'page' => 'account',
1093
- 'module_id' => $fs->get_id(),
1094
- 'module_type' => $fs->get_module_type(),
1095
- 'module_slug' => $slug,
1096
- 'module_version' => $fs->get_plugin_version(),
1097
- );
1098
  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 $fs
16
+ */
17
+ $fs = freemius( $VARS['id'] );
18
+
19
+ $slug = $fs->get_slug();
20
+
21
+ /**
22
+ * @var FS_Plugin_Tag $update
23
+ */
24
+ $update = $fs->has_release_on_freemius() ?
25
+ $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ) :
26
+ null;
27
+
28
+ if ( is_object($update) ) {
29
+ /**
30
+ * This logic is particularly required for multisite environment.
31
+ * If a module is site activated (not network) and not on the main site,
32
+ * the module will NOT be executed on the network level, therefore, the
33
+ * custom updates logic will not be executed as well, so unless we force
34
+ * the injection of the update into the updates transient, premium updates
35
+ * will not work.
36
+ *
37
+ * @author Vova Feldman (@svovaf)
38
+ * @since 2.0.0
39
+ */
40
+ $updater = FS_Plugin_Updater::instance( $fs );
41
+ $updater->set_update_data( $update );
42
+ }
43
+
44
+ $is_paying = $fs->is_paying();
45
+ $user = $fs->get_user();
46
+ $site = $fs->get_site();
47
+ $name = $user->get_name();
48
+ $license = $fs->_get_license();
49
+ $is_data_debug_mode = $fs->is_data_debug_mode();
50
+ $is_whitelabeled = $fs->is_whitelabeled();
51
+ $subscription = ( is_object( $license ) ?
52
+ $fs->_get_subscription( $license->id ) :
53
+ null );
54
+ $plan = $fs->get_plan();
55
+ $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() );
56
+ $is_paid_trial = $fs->is_paid_trial();
57
+ $has_paid_plan = $fs->apply_filters( 'has_paid_plan_account', $fs->has_paid_plan() );
58
+ $show_upgrade = ( ! $is_whitelabeled && $has_paid_plan && ! $is_paying && ! $is_paid_trial );
59
+ $trial_plan = $fs->get_trial_plan();
60
+
61
+ if ( $has_paid_plan ) {
62
+ $fs->_add_license_activation_dialog_box();
63
+ }
64
+
65
+ $ids_of_installs_activated_with_foreign_licenses = $fs->should_handle_user_change() ?
66
+ $fs->get_installs_ids_with_foreign_licenses() :
67
+ array();
68
+
69
+ if ( ! empty( $ids_of_installs_activated_with_foreign_licenses ) ) {
70
+ $fs->_add_user_change_dialog_box( $ids_of_installs_activated_with_foreign_licenses );
71
+ }
72
+
73
+ if ( $fs->is_whitelabeled( true ) || $fs->is_data_debug_mode() ) {
74
+ $fs->_add_data_debug_mode_dialog_box();
75
+ }
76
+
77
+ if ( fs_request_get_bool( 'auto_install' ) ) {
78
+ $fs->_add_auto_installation_dialog_box();
79
+ }
80
+
81
+ if ( fs_request_get_bool( 'activate_license' ) ) {
82
+ // Open the license activation dialog box on the account page.
83
+ add_action( 'admin_footer', array(
84
+ &$fs,
85
+ '_open_license_activation_dialog_box'
86
+ ) );
87
+ }
88
+
89
+ $payments = $fs->_fetch_payments();
90
+
91
+ $show_billing = ( ! $is_whitelabeled && is_array( $payments ) && 0 < count( $payments ) );
92
+
93
+
94
+ $has_tabs = $fs->_add_tabs_before_content();
95
+
96
+ if ( $has_tabs ) {
97
+ $query_params['tabs'] = 'true';
98
+ }
99
+
100
+ // Aliases.
101
+ $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug );
102
+ $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug );
103
+ $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug );
104
+ /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */
105
+ $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 );
106
+ $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 );
107
+ $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug );
108
+ $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 );
109
+ $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 );
110
+ /* translators: %s: Plan title (e.g. "Professional") */
111
+ $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug );
112
+ $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug );
113
+ /* translators: %s: Time period (e.g. Auto renews in "2 months") */
114
+ $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug );
115
+ /* translators: %s: Time period (e.g. Expires in "2 months") */
116
+ $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug );
117
+ $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug );
118
+ $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug );
119
+ $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug );
120
+ $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug );
121
+ $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug );
122
+ $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug );
123
+ $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug );
124
+ $free_text = fs_text_inline( 'Free', 'free', $slug );
125
+ $activate_text = fs_text_inline( 'Activate', 'activate', $slug );
126
+ $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug );
127
+ $bundle_plan_text = fs_text_inline( 'Bundle Plan', 'bundle-plan', $slug );
128
+
129
+ $show_plan_row = true;
130
+ $show_license_row = is_object( $license );
131
+
132
+ $site_view_params = array();
133
+
134
+ if ( fs_is_network_admin() ) {
135
+ $sites = Freemius::get_sites();
136
+ $all_installs_plan_id = null;
137
+ $all_installs_license_id = ( $show_license_row ? $license->id : null );
138
+ foreach ( $sites as $s ) {
139
+ $site_info = $fs->get_site_info( $s );
140
+ $install = $fs->get_install_by_blog_id( $site_info['blog_id'] );
141
+ $view_params = array(
142
+ 'freemius' => $fs,
143
+ 'license' => $license,
144
+ 'site' => $site_info,
145
+ 'install' => $install,
146
+ );
147
+
148
+ $site_view_params[] = $view_params;
149
+
150
+ if ( empty( $install ) ) {
151
+ continue;
152
+ }
153
+
154
+ if ( $show_plan_row ) {
155
+ if ( is_null( $all_installs_plan_id ) ) {
156
+ $all_installs_plan_id = $install->plan_id;
157
+ } else if ( $all_installs_plan_id != $install->plan_id ) {
158
+ $show_plan_row = false;
159
+ }
160
+ }
161
+
162
+ if ( $show_license_row && $all_installs_license_id != $install->license_id ) {
163
+ $show_license_row = false;
164
+ }
165
+ }
166
+ }
167
+
168
+ $has_bundle_license = false;
169
+
170
+ if ( is_object( $license ) &&
171
+ FS_Plugin_License::is_valid_id( $license->parent_license_id )
172
+ ) {
173
+ // Context license has a parent license, therefore, the account has a bundle license.
174
+ $has_bundle_license = true;
175
+ }
176
+
177
+ $bundle_subscription = null;
178
+ $is_bundle_first_payment_pending = false;
179
+
180
+ if (
181
+ $show_plan_row &&
182
+ is_object( $license ) &&
183
+ $has_bundle_license
184
+ ) {
185
+ $bundle_plan_title = strtoupper( $license->parent_plan_title );
186
+ $bundle_subscription = $fs->_get_subscription( $license->parent_license_id );
187
+ $is_bundle_first_payment_pending = $license->is_first_payment_pending();
188
+ }
189
+
190
+ $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ?
191
+ get_current_blog_id() :
192
+ 0;
193
+
194
+ $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id );
195
+
196
+ $is_premium = $fs->is_premium();
197
+
198
+ $account_addons = $fs->get_updated_account_addons();
199
+ $installed_addons = $fs->get_installed_addons();
200
+ $installed_addons_ids = array();
201
+
202
+ /**
203
+ * Store the installed add-ons' IDs into a collection which will be used in determining the add-ons to show on the "Account" page, and at the same time try to find an add-on that is activated with a bundle license if the core product is not.
204
+ *
205
+ * @author Leo Fajardo
206
+ *
207
+ * @since 2.4.0
208
+ */
209
+ foreach ( $installed_addons as $fs_addon ) {
210
+ $installed_addons_ids[] = $fs_addon->get_id();
211
+
212
+ if ( $has_bundle_license ) {
213
+ // We already have the context bundle license details, skip.
214
+ continue;
215
+ }
216
+
217
+ if (
218
+ $show_plan_row &&
219
+ $fs_addon->has_active_valid_license()
220
+ ) {
221
+ $addon_license = $fs_addon->_get_license();
222
+
223
+ if ( FS_Plugin_License::is_valid_id( $addon_license->parent_license_id ) ) {
224
+ // Add-on's license is associated with a parent/bundle license.
225
+ $has_bundle_license = true;
226
+
227
+ $bundle_plan_title = strtoupper( $addon_license->parent_plan_title );
228
+ $bundle_subscription = $fs_addon->_get_subscription( $addon_license->parent_license_id );
229
+ $is_bundle_first_payment_pending = $addon_license->is_first_payment_pending();
230
+ }
231
+ }
232
+ }
233
+
234
+ $addons_to_show = array_unique( array_merge( $installed_addons_ids, $account_addons ) );
235
+
236
+ $is_active_bundle_subscription = ( is_object( $bundle_subscription ) && $bundle_subscription->is_active() );
237
+ ?>
238
+ <div class="wrap fs-section">
239
+ <?php if ( ! $has_tabs && ! $fs->apply_filters( 'hide_account_tabs', false ) ) : ?>
240
+ <h2 class="nav-tab-wrapper">
241
+ <a href="<?php echo $fs->get_account_url() ?>"
242
+ class="nav-tab nav-tab-active"><?php fs_esc_html_echo_inline( 'Account', 'account', $slug ) ?></a>
243
+ <?php if ( $fs->has_addons() ) : ?>
244
+ <a href="<?php echo $fs->_get_admin_page_url( 'addons' ) ?>"
245
+ class="nav-tab"><?php echo esc_html( $addons_text ) ?></a>
246
+ <?php endif ?>
247
+ <?php if ( $show_upgrade ) : ?>
248
+ <a href="<?php echo $fs->get_upgrade_url() ?>" class="nav-tab"><?php echo esc_html( $upgrade_text ) ?></a>
249
+ <?php if ( $fs->apply_filters( 'show_trial', true ) && ! $fs->is_trial_utilized() && $fs->has_trial_plan() ) : ?>
250
+ <a href="<?php echo $fs->get_trial_url() ?>" class="nav-tab"><?php fs_esc_html_echo_inline( 'Free Trial', 'free-trial', $slug ) ?></a>
251
+ <?php endif ?>
252
+ <?php endif ?>
253
+ </h2>
254
+ <?php endif ?>
255
+
256
+ <div id="poststuff">
257
+ <div id="fs_account">
258
+ <div class="has-sidebar has-right-sidebar">
259
+ <div class="has-sidebar-content">
260
+ <div class="postbox">
261
+ <h3><span class="dashicons dashicons-businessman"></span> <?php fs_esc_html_echo_inline( 'Account Details', 'account-details', $slug ) ?></h3>
262
+ <div class="fs-header-actions">
263
+ <ul>
264
+ <?php if ( $fs->is_whitelabeled( true ) ) : ?>
265
+ <li>
266
+ <a href="#" class="debug-license-trigger"><i class="dashicons dashicons-<?php echo $is_whitelabeled ? 'editor-code' : 'controls-pause' ?>"></i> <span><?php
267
+ if ( $is_whitelabeled ) {
268
+ fs_esc_html_echo_inline( 'Start Debug', 'start-debug-license', $slug );
269
+ } else {
270
+ fs_esc_html_echo_inline( 'Stop Debug', 'stop-debug-license', $slug );
271
+ }
272
+ ?></span></a>
273
+ </li>
274
+ <li>&nbsp;&bull;&nbsp;</li>
275
+ <?php endif ?>
276
+ <?php if ( $show_billing ) : ?>
277
+ <li><a href="#fs_billing"><i class="dashicons dashicons-portfolio"></i> <?php fs_esc_html_echo_inline( 'Billing & Invoices', 'billing-invoices', $slug ) ?></li>
278
+ <li>&nbsp;&bull;&nbsp;</li>
279
+ <?php endif ?>
280
+ <?php if ( ! $is_whitelabeled ) : ?>
281
+ <?php if ( ! $is_paying ) : ?>
282
+ <li>
283
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
284
+ <input type="hidden" name="fs_action" value="delete_account">
285
+ <?php wp_nonce_field( 'delete_account' ) ?>
286
+ <a class="fs-delete-account" href="#" onclick="if (confirm('<?php
287
+ if ( $is_active_subscription ) {
288
+ echo esc_attr( sprintf( fs_text_inline( '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?', 'delete-account-x-confirm', $slug ), $plan->title ) );
289
+ } else {
290
+ echo esc_attr( sprintf( fs_text_inline( '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?', 'delete-account-confirm', $slug ), $fs->get_module_label( true ) ) );
291
+ }
292
+ ?>')) this.parentNode.submit(); return false;"><i
293
+ class="dashicons dashicons-no"></i> <?php fs_esc_html_echo_inline( 'Delete Account', 'delete-account', $slug ) ?></a>
294
+ </form>
295
+ </li>
296
+ <li>&nbsp;&bull;&nbsp;</li>
297
+ <?php endif ?>
298
+ <?php if ( $is_paying ) : ?>
299
+ <?php if ( ! fs_is_network_admin() ) : ?>
300
+ <li>
301
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
302
+ <input type="hidden" name="fs_action" value="deactivate_license">
303
+ <?php wp_nonce_field( 'deactivate_license' ) ?>
304
+ <a href="#" class="fs-deactivate-license"><i
305
+ class="dashicons dashicons-admin-network"></i> <?php fs_echo_inline( 'Deactivate License', 'deactivate-license', $slug ) ?>
306
+ </a>
307
+ </form>
308
+ </li>
309
+ <li>&nbsp;&bull;&nbsp;</li>
310
+ <?php endif ?>
311
+ <?php if ( ! $license->is_lifetime() &&
312
+ $is_active_subscription
313
+ ) : ?>
314
+ <li>
315
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
316
+ <input type="hidden" name="fs_action" value="downgrade_account">
317
+ <?php wp_nonce_field( 'downgrade_account' ) ?>
318
+ <a href="#"
319
+ onclick="if ( confirm('<?php echo esc_attr( sprintf(
320
+ $downgrade_x_confirm_text,
321
+ ( $fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ),
322
+ $plan->title,
323
+ human_time_diff( time(), strtotime( $license->expiration ) )
324
+ ) ) ?> <?php if ( ! $license->is_block_features ) {
325
+ echo esc_attr( sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) );
326
+ } else {
327
+ echo esc_attr( sprintf( $after_downgrade_blocking_text, $plan->title ) );
328
+ }?> <?php echo esc_attr( $prices_increase_text ) ?> <?php fs_esc_attr_echo_inline( 'Are you sure you want to proceed?', 'proceed-confirmation', $slug ) ?>') ) this.parentNode.submit(); return false;"><i class="dashicons dashicons-download"></i> <?php echo esc_html( $fs->is_only_premium() ? fs_text_inline( 'Cancel Subscription', 'cancel-subscription', $slug ) : $downgrade_text ) ?></a>
329
+ </form>
330
+ </li>
331
+ <li>&nbsp;&bull;&nbsp;</li>
332
+ <?php endif ?>
333
+ <?php if ( ! $fs->is_single_plan() ) : ?>
334
+ <li>
335
+ <a href="<?php echo $fs->get_upgrade_url() ?>"><i
336
+ class="dashicons dashicons-grid-view"></i> <?php echo esc_html( $change_plan_text ) ?></a>
337
+ </li>
338
+ <li>&nbsp;&bull;&nbsp;</li>
339
+ <?php endif ?>
340
+ <?php elseif ( $is_paid_trial ) : ?>
341
+ <li>
342
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
343
+ <input type="hidden" name="fs_action" value="cancel_trial">
344
+ <?php wp_nonce_field( 'cancel_trial' ) ?>
345
+ <a href="#" class="fs-cancel-trial"><i
346
+ class="dashicons dashicons-download"></i> <?php echo esc_html( $cancel_trial_text ) ?></a>
347
+ </form>
348
+ </li>
349
+ <li>&nbsp;&bull;&nbsp;</li>
350
+ <?php endif ?>
351
+ <?php endif ?>
352
+ <li>
353
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
354
+ <input type="hidden" name="fs_action" value="<?php echo $fs->get_unique_affix() ?>_sync_license">
355
+ <?php wp_nonce_field( $fs->get_unique_affix() . '_sync_license' ) ?>
356
+ <a href="#" onclick="this.parentNode.submit(); return false;"><i
357
+ class="dashicons dashicons-image-rotate"></i> <?php fs_esc_html_echo_x_inline( 'Sync', 'as synchronize', 'sync', $slug ) ?></a>
358
+ </form>
359
+ </li>
360
+ </ul>
361
+ </div>
362
+ <div class="inside">
363
+ <table id="fs_account_details" cellspacing="0" class="fs-key-value-table">
364
+ <?php
365
+ $hide_license_key = ( ! $show_license_row || $fs->apply_filters( 'hide_license_key', false ) );
366
+
367
+ $profile = array();
368
+
369
+ if ( ! $is_whitelabeled ) {
370
+ $profile[] = array(
371
+ 'id' => 'user_name',
372
+ 'title' => fs_text_inline( 'Name', 'name', $slug ),
373
+ 'value' => $name
374
+ );
375
+ // if (isset($user->email) && false !== strpos($user->email, '@'))
376
+ $profile[] = array(
377
+ 'id' => 'email',
378
+ 'title' => fs_text_inline( 'Email', 'email', $slug ),
379
+ 'value' => $user->email
380
+ );
381
+
382
+ if ( is_numeric( $user->id ) ) {
383
+ $profile[] = array(
384
+ 'id' => 'user_id',
385
+ 'title' => fs_text_inline( 'User ID', 'user-id', $slug ),
386
+ 'value' => $user->id
387
+ );
388
+ }
389
+ }
390
+
391
+ $profile[] = array(
392
+ 'id' => 'product',
393
+ 'title' => ( $fs->is_plugin() ?
394
+ fs_text_inline( 'Plugin', 'plugin', $slug ) :
395
+ fs_text_inline( 'Theme', 'theme', $slug ) ),
396
+ 'value' => $fs->get_plugin_title()
397
+ );
398
+
399
+ $profile[] = array(
400
+ 'id' => 'product_id',
401
+ 'title' => ( $fs->is_plugin() ?
402
+ fs_text_inline( 'Plugin', 'plugin', $slug ) :
403
+ fs_text_inline( 'Theme', 'theme', $slug ) ) . ' ' . fs_text_inline( 'ID', 'id', $slug ),
404
+ 'value' => $fs->get_id()
405
+ );
406
+
407
+ if ( ! fs_is_network_admin()) {
408
+ $profile[] = array(
409
+ 'id' => 'site_id',
410
+ 'title' => fs_text_inline( 'Site ID', 'site-id', $slug ),
411
+ 'value' => is_string( $site->id ) ?
412
+ $site->id :
413
+ fs_text_inline( 'No ID', 'no-id', $slug )
414
+ );
415
+
416
+ $profile[] = array(
417
+ 'id' => 'site_public_key',
418
+ 'title' => fs_text_inline( 'Public Key', 'public-key', $slug ),
419
+ 'value' => $site->public_key
420
+ );
421
+
422
+ $profile[] = array(
423
+ 'id' => 'site_secret_key',
424
+ 'title' => fs_text_inline( 'Secret Key', 'secret-key', $slug ),
425
+ 'value' => ( ( is_string( $site->secret_key ) ) ?
426
+ $site->secret_key :
427
+ fs_text_x_inline( 'No Secret', 'as secret encryption key missing', 'no-secret', $slug )
428
+ )
429
+ );
430
+ }
431
+
432
+ $profile[] = array(
433
+ 'id' => 'version',
434
+ 'title' => $version_text,
435
+ 'value' => $fs->get_plugin_version()
436
+ );
437
+
438
+ if ( ! fs_is_network_admin() && $is_premium && ! $is_whitelabeled ) {
439
+ $profile[] = array(
440
+ 'id' => 'beta_program',
441
+ 'title' => '',
442
+ 'value' => $site->is_beta
443
+ );
444
+ }
445
+
446
+ if ( $has_paid_plan || $has_bundle_license ) {
447
+ if ( $fs->is_trial() ) {
448
+ if ( $show_plan_row ) {
449
+ $profile[] = array(
450
+ 'id' => 'plan',
451
+ 'title' => $plan_text,
452
+ 'value' => ( is_string( $trial_plan->name ) ?
453
+ strtoupper( $trial_plan->title ) :
454
+ fs_text_inline( 'Trial', 'trial', $slug ) )
455
+ );
456
+ }
457
+ } else {
458
+ if ( $show_plan_row ) {
459
+ $profile[] = array(
460
+ 'id' => 'plan',
461
+ 'title' => ( $has_bundle_license ? ucfirst( $fs->get_module_type() ) . ' ' : '' ) . $plan_text,
462
+ 'value' => strtoupper( is_string( $plan->name ) ?
463
+ $plan->title :
464
+ strtoupper( $free_text )
465
+ )
466
+ );
467
+
468
+ if ( $has_bundle_license ) {
469
+ $profile[] = array(
470
+ 'id' => 'bundle_plan',
471
+ 'title' => $bundle_plan_text,
472
+ 'value' => $bundle_plan_title
473
+ );
474
+ }
475
+ }
476
+
477
+ if ( is_object( $license ) ) {
478
+ if ( ! $hide_license_key ) {
479
+ $profile[] = array(
480
+ 'id' => 'license_key',
481
+ 'title' => fs_text_inline( 'License Key', $slug ),
482
+ 'value' => $license->secret_key,
483
+ );
484
+ }
485
+ }
486
+ }
487
+ }
488
+ ?>
489
+ <?php $odd = true;
490
+ foreach ( $profile as $p ) : ?>
491
+ <?php
492
+ if ( 'plan' === $p['id'] && ! $has_paid_plan ) {
493
+ // If plugin don't have any paid plans, there's no reason
494
+ // to show current plan.
495
+ continue;
496
+ }
497
+ ?>
498
+ <tr class="fs-field-<?php echo $p['id'] ?><?php if ( $odd ) : ?> alternate<?php endif ?>">
499
+ <td>
500
+ <nobr><?php echo $p['title'] ?><?php echo ( ! empty( $p['title'] ) ) ? ':' : '' ?></nobr>
501
+ </td>
502
+ <td<?php if ( 'plan' === $p['id'] || 'bundle_plan' === $p['id'] ) { echo ' colspan="2"'; }?>>
503
+ <?php if ( in_array( $p['id'], array( 'license_key', 'site_secret_key' ) ) ) : ?>
504
+ <code><?php echo FS_Plugin_License::mask_secret_key_for_html( $p['value'] ) ?></code>
505
+ <?php if ( ! $is_whitelabeled ) : ?>
506
+ <input type="text" value="<?php echo htmlspecialchars( $p['value'] ) ?>" style="display: none"
507
+ readonly/>
508
+ <?php endif ?>
509
+ <?php elseif ( 'beta_program' === $p['id'] ) : ?>
510
+ <label>
511
+ <input type="checkbox" class="fs-toggle-beta-mode" <?php checked( true, $p['value'] ) ?>/><span><?php
512
+ fs_esc_html_echo_inline( 'Join the Beta program', 'join-beta', $slug )
513
+ ?></span></label>
514
+ <?php else : ?>
515
+ <code><?php echo htmlspecialchars( $p['value'] ) ?></code>
516
+ <?php endif ?>
517
+ <?php if ( 'email' === $p['id'] && ! $user->is_verified() ) : ?>
518
+ <label class="fs-tag fs-warn"><?php fs_esc_html_echo_inline( 'not verified', 'not-verified', $slug ) ?></label>
519
+ <?php endif ?>
520
+ <?php if ( 'plan' === $p['id'] ) : ?>
521
+ <?php if ( $fs->is_trial() ) : ?>
522
+ <label class="fs-tag fs-success"><?php echo esc_html( $trial_text ) ?></label>
523
+ <?php endif ?>
524
+ <?php if ( is_object( $license ) && ! $license->is_lifetime() ) : ?>
525
+ <?php if ( ! $is_active_subscription && ! $is_active_bundle_subscription && ! $license->is_first_payment_pending() ) : ?>
526
+ <?php $is_license_expired = $license->is_expired() ?>
527
+ <?php $expired_ago_text = ( fs_text_inline( 'Expired', 'expired', $slug ) . ' ' . fs_text_x_inline( '%s ago', 'x-ago', $slug ) ) ?>
528
+ <label
529
+ class="fs-tag <?php echo $is_license_expired ? 'fs-error' : 'fs-warn' ?>"><?php
530
+ echo esc_html( sprintf( $is_license_expired ? $expired_ago_text : $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ) )
531
+ ?></label>
532
+ <?php elseif ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) : ?>
533
+ <label class="fs-tag fs-success"><?php echo esc_html( sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ) ) ?></label>
534
+ <?php endif ?>
535
+ <?php elseif ( $fs->is_trial() ) : ?>
536
+ <label class="fs-tag fs-warn"><?php echo esc_html( sprintf( $expires_in_text, human_time_diff( time(), strtotime( $site->trial_ends ) ) ) ) ?></label>
537
+ <?php endif ?>
538
+ <?php if ( ! $is_whitelabeled ) : ?>
539
+ <div class="button-group">
540
+ <?php $available_license = $fs->is_free_plan() && ! fs_is_network_admin() ? $fs->_get_available_premium_license( $site->is_localhost() ) : false ?>
541
+ <?php if ( is_object( $available_license ) ) : ?>
542
+ <?php $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id ) ?>
543
+ <?php
544
+ $view_params = array(
545
+ 'freemius' => $fs,
546
+ 'slug' => $slug,
547
+ 'license' => $available_license,
548
+ 'plan' => $premium_plan,
549
+ 'is_localhost' => $site->is_localhost(),
550
+ 'install_id' => $site->id,
551
+ 'class' => 'button-primary',
552
+ );
553
+ fs_require_template( 'account/partials/activate-license-button.php', $view_params ); ?>
554
+ <?php else : ?>
555
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>"
556
+ method="POST" class="button-group">
557
+ <?php if ( $show_upgrade && $is_premium ) : ?>
558
+ <a class="button activate-license-trigger <?php echo $fs->get_unique_affix() ?>" href="#"><?php fs_esc_html_echo_inline( 'Activate License', 'activate-license', $slug ) ?></a>
559
+ <?php endif ?>
560
+ <input type="submit" class="button"
561
+ value="<?php echo esc_attr( $sync_license_text ) ?>">
562
+ <input type="hidden" name="fs_action"
563
+ value="<?php echo $fs->get_unique_affix() ?>_sync_license">
564
+ <?php wp_nonce_field( $fs->get_unique_affix() . '_sync_license' ) ?>
565
+ <?php if ( $show_upgrade || ! $fs->is_single_plan() ) : ?>
566
+ <a href="<?php echo $fs->get_upgrade_url() ?>"
567
+ class="button<?php
568
+ echo $show_upgrade ?
569
+ ' button-primary fs-upgrade' :
570
+ ' fs-change-plan'; ?> button-upgrade"><i
571
+ class="dashicons dashicons-cart"></i> <?php echo esc_html( $show_upgrade ? $upgrade_text : $change_plan_text ) ?></a>
572
+ <?php endif ?>
573
+ </form>
574
+ <?php endif ?>
575
+ </div>
576
+ <?php endif ?>
577
+ <?php elseif ( 'bundle_plan' === $p['id'] ) : ?>
578
+ <?php if ( is_object( $bundle_subscription ) ) : ?>
579
+ <?php if ( $is_active_bundle_subscription && ! $is_bundle_first_payment_pending ) : ?>
580
+ <label class="fs-tag fs-success"><?php echo esc_html( sprintf( $renews_in_text, human_time_diff( time(), strtotime( $bundle_subscription->next_payment ) ) ) ) ?></label>
581
+ <?php endif ?>
582
+ <?php endif ?>
583
+ <?php elseif ( 'version' === $p['id'] && $has_paid_plan ) : ?>
584
+ <?php if ( $fs->has_premium_version() ) : ?>
585
+ <?php if ( $is_premium ) : ?>
586
+ <label
587
+ class="fs-tag fs-<?php echo $fs->can_use_premium_code() ? 'success' : 'warn' ?>"><?php fs_esc_html_echo_inline( 'Premium version', 'premium-version', $slug ) ?></label>
588
+ <?php elseif ( $fs->can_use_premium_code() ) : ?>
589
+ <label class="fs-tag fs-warn"><?php fs_esc_html_echo_inline( 'Free version', 'free-version', $slug ) ?></label>
590
+ <?php endif ?>
591
+ <?php endif ?>
592
+ <?php endif ?>
593
+ </td>
594
+ <?php if ( 'plan' !== $p['id'] && 'bundle_plan' !== $p['id'] ) : ?>
595
+ <td class="fs-right">
596
+ <?php if ( 'email' === $p['id'] && ! $user->is_verified() ) : ?>
597
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST">
598
+ <input type="hidden" name="fs_action" value="verify_email">
599
+ <?php wp_nonce_field( 'verify_email' ) ?>
600
+ <input type="submit" class="button button-small"
601
+ value="<?php fs_esc_attr_echo_inline( 'Verify Email', 'verify-email', $slug ) ?>">
602
+ </form>
603
+ <?php endif ?>
604
+ <?php if ( 'version' === $p['id'] ) : ?>
605
+ <?php if ( $fs->has_release_on_freemius() ) : ?>
606
+ <div class="button-group">
607
+ <?php if ( $is_paying || $fs->is_trial() ) : ?>
608
+ <?php if ( ! $fs->is_allowed_to_install() ) : ?>
609
+ <a target="_blank" rel="noopener" class="button button-primary"
610
+ href="<?php echo $fs->_get_latest_download_local_url() ?>"><?php
611
+ $download_version_text_suffix = ( is_object( $update ) ? ' [' . $update->version . ']' : '' );
612
+
613
+ $download_version_text = sprintf(
614
+ /* translators: %s: plan name (e.g. Download "Professional" Version) */
615
+ fs_text_inline( 'Download %s Version', 'download-x-version', $slug ),
616
+ ( $fs->is_trial() ? $trial_plan->title : $plan->title )
617
+ ) .
618
+ $download_version_text_suffix;
619
+
620
+ $download_version_text_length = function_exists( 'mb_strlen' ) ?
621
+ mb_strlen( $download_version_text ) :
622
+ strlen( $download_version_text );
623
+
624
+ if ( $download_version_text_length > 31 ) {
625
+ /**
626
+ * Try to limit the number of characters to 31 for now.
627
+ *
628
+ * @author Leo Fajardo (@leorw)
629
+ * @aince 2.3.2
630
+ */
631
+ $download_version_text = fs_text_inline( 'Download Paid Version', 'download-paid-version', $slug ) . $download_version_text_suffix;
632
+ }
633
+
634
+ echo $download_version_text;
635
+ ?></a>
636
+ <?php elseif ( is_object( $update ) ) : ?>
637
+ <?php
638
+ $module_type = $fs->get_module_type();
639
+ ?>
640
+ <a class="button button-primary"
641
+ href="<?php echo wp_nonce_url( self_admin_url( "update.php?action=upgrade-{$module_type}&{$module_type}=" . $fs->get_plugin_basename() ), "upgrade-{$module_type}_" . $fs->get_plugin_basename() ) ?>"><?php echo fs_esc_html_inline( 'Install Update Now', 'install-update-now', $slug ) . ' [' . $update->version . ']' ?></a>
642
+ <?php endif ?>
643
+ <?php endif; ?>
644
+ </div>
645
+ <?php endif ?>
646
+ <?php
647
+ elseif ( in_array( $p['id'], array( 'license_key', 'site_secret_key' ) ) ) : ?>
648
+ <?php if ( ! $is_whitelabeled ) : ?>
649
+ <button class="button button-small fs-toggle-visibility"><?php fs_esc_html_echo_x_inline( 'Show', 'verb', 'show', $slug ) ?></button>
650
+ <?php endif ?>
651
+ <?php if ('license_key' === $p['id']) : ?>
652
+ <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>
653
+ <?php endif ?>
654
+ <?php
655
+ elseif (/*in_array($p['id'], array('site_secret_key', 'site_id', 'site_public_key')) ||*/
656
+ ( is_string( $user->secret_key ) && in_array( $p['id'], array(
657
+ 'email',
658
+ 'user_name'
659
+ ) ) )
660
+ ) : ?>
661
+ <form action="<?php echo $fs->_get_admin_page_url( 'account' ) ?>" method="POST"
662
+ onsubmit="var val = prompt('<?php echo esc_attr( sprintf(
663
+ /* translators: %s: User's account property (e.g. name, email) */
664
+ fs_text_inline( 'What is your %s?', 'what-is-your-x', $slug ),
665
+ $p['title']
666
+ ) ) ?>', '<?php echo $p['value'] ?>'); if (null == val || '' === val) return false; jQuery('input[name=fs_<?php echo $p['id'] ?>_<?php echo $fs->get_unique_affix() ?>]').val(val); return true;">
667
+ <input type="hidden" name="fs_action" value="update_<?php echo $p['id'] ?>">
668
+ <input type="hidden" name="fs_<?php echo $p['id'] ?>_<?php echo $fs->get_unique_affix() ?>"
669
+ value="">
670
+ <?php wp_nonce_field( 'update_' . $p['id'] ) ?>
671
+ <input type="submit" class="button button-small"
672
+ value="<?php echo fs_esc_attr_x_inline( 'Edit', 'verb', 'edit', $slug ) ?>">
673
+ </form>
674
+ <?php elseif ( 'user_id' === $p['id'] && ! empty( $ids_of_installs_activated_with_foreign_licenses ) ) : ?>
675
+ <input id="fs_change_user" type="submit" class="button button-small"
676
+ value="<?php echo fs_esc_attr_inline( 'Change User', 'change-user', $slug ) ?>">
677
+ <?php endif ?>
678
+ </td>
679
+ <?php endif ?>
680
+ </tr>
681
+ <?php
682
+ if ( 'version' === $p['id'] && $is_premium ) {
683
+ /**
684
+ * If there's a row for the beta program, keep its background color
685
+ * the same as the version info row.
686
+ *
687
+ * @author Leo Fajardo (@leorw)
688
+ * @since 2.3.0
689
+ */
690
+ continue;
691
+ }
692
+
693
+ $odd = ! $odd;
694
+ endforeach ?>
695
+ </table>
696
+ </div>
697
+ </div>
698
+ <?php if ( fs_is_network_admin() ) : ?>
699
+ <div id="fs_sites" class="postbox">
700
+ <h3><span class="dashicons dashicons-networking"></span> <?php fs_esc_html_echo_inline( 'Sites', 'sites', $slug ) ?></h3>
701
+ <div class="fs-header-actions">
702
+ <?php if ( ! $is_whitelabeled ) : ?>
703
+ <?php $has_license = is_object( $license ) ?>
704
+ <?php if ( $has_license || ( $show_upgrade && $is_premium ) ) : ?>
705
+ <?php
706
+ $activate_license_button_text = $has_license ?
707
+ fs_esc_html_inline( 'Change License', 'change-license', $slug ) :
708
+ fs_esc_html_inline( 'Activate License', 'activate-license', $slug );
709
+ ?>
710
+ <a class="button<?php echo ( ! $has_license ? ' button-primary' : '' ) ?> activate-license-trigger <?php echo $fs->get_unique_affix() ?>" href="#"><?php echo $activate_license_button_text ?></a>
711
+ <?php endif ?>
712
+ <?php endif ?>
713
+ <input class="fs-search" type="text" placeholder="<?php fs_esc_attr_echo_inline( 'Search by address', 'search-by-address', $slug ) ?>..."><span class="dashicons dashicons-search"></span>
714
+ </div>
715
+ <div class="inside">
716
+ <div id="" class="fs-scrollable-table">
717
+ <div class="fs-table-head">
718
+ <table class="widefat">
719
+ <thead>
720
+ <tr>
721
+ <td><?php fs_esc_html_echo_inline('ID', 'id', $slug) ?></td>
722
+ <td><?php fs_esc_html_echo_inline('Address', 'address', $slug) ?></td>
723
+ <td><?php fs_esc_html_echo_inline('License', 'license', $slug) ?></td>
724
+ <td><?php fs_esc_html_echo_inline('Plan', 'plan', $slug) ?></td>
725
+ <td></td>
726
+ </tr>
727
+ </thead>
728
+ </table>
729
+ </div>
730
+ <div class="fs-table-body">
731
+ <table class="widefat">
732
+ <?php
733
+ foreach ( $site_view_params as $view_params ) {
734
+ fs_require_template(
735
+ 'account/partials/site.php',
736
+ $view_params
737
+ );
738
+ } ?>
739
+ </table>
740
+ </div>
741
+ </div>
742
+ </div>
743
+ </div>
744
+ <?php endif ?>
745
+ <?php if ( 0 < count( $addons_to_show ) ) : ?>
746
+ <!-- Add-Ons -->
747
+ <div class="postbox">
748
+ <div class="">
749
+ <!-- <div class="inside">-->
750
+ <table id="fs_addons" class="widefat">
751
+ <thead>
752
+ <tr>
753
+ <th><h3><?php echo esc_html( $addons_text ) ?></h3></th>
754
+ <th><?php fs_esc_html_echo_inline( 'ID', 'id', $slug ) ?></th>
755
+ <th><?php echo esc_html( $version_text ) ?></th>
756
+ <th><?php echo esc_html( $plan_text ) ?></th>
757
+ <th><?php fs_esc_html_echo_x_inline( 'License', 'as software license', 'license', $slug ) ?></th>
758
+ <th></th>
759
+ <?php if ( defined( 'WP_FS__DEV_MODE' ) && WP_FS__DEV_MODE ) : ?>
760
+ <th></th>
761
+ <?php endif ?>
762
+ </tr>
763
+ </thead>
764
+ <tbody>
765
+ <?php
766
+ $odd = true;
767
+
768
+ $installed_addons_ids_map = array_flip( $installed_addons_ids );
769
+
770
+ $addon_info_by_id = array();
771
+ $hide_all_addons_data = false;
772
+
773
+ if ( $fs->is_whitelabeled_by_flag() ) {
774
+ $hide_all_addons_data = true;
775
+
776
+ foreach ( $addons_to_show as $addon_id ) {
777
+ $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] );
778
+ $addon_info = $fs->_get_addon_info( $addon_id, $is_addon_installed );
779
+ $is_addon_connected = $addon_info['is_connected'];
780
+
781
+ $fs_addon = ( $is_addon_connected && $is_addon_installed ) ?
782
+ freemius( $addon_id ) :
783
+ null;
784
+
785
+ $is_whitelabeled = is_object( $fs_addon ) ?
786
+ $fs_addon->is_whitelabeled( true ) :
787
+ $addon_info['is_whitelabeled'];
788
+
789
+ if ( ! $is_whitelabeled ) {
790
+ $hide_all_addons_data = false;
791
+ }
792
+
793
+ if ( $is_data_debug_mode ) {
794
+ $is_whitelabeled = false;
795
+ }
796
+
797
+ $addon_info_by_id[ $addon_id ] = $addon_info;
798
+ }
799
+ }
800
+
801
+ foreach ( $addons_to_show as $addon_id ) {
802
+ $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] );
803
+
804
+ if (
805
+ $hide_all_addons_data &&
806
+ ! $is_addon_installed &&
807
+ ! file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $fs->get_addon_basename( $addon_id ) ) )
808
+ ) {
809
+ continue;
810
+ }
811
+
812
+ $addon_view_params = array(
813
+ 'parent_fs' => $fs,
814
+ 'addon_id' => $addon_id,
815
+ 'odd' => $odd,
816
+ 'fs_blog_id' => $fs_blog_id,
817
+ 'active_plugins_directories_map' => &$active_plugins_directories_map,
818
+ 'is_addon_installed' => $is_addon_installed,
819
+ 'addon_info' => isset( $addon_info_by_id[ $addon_id ] ) ?
820
+ $addon_info_by_id[ $addon_id ] :
821
+ $fs->_get_addon_info( $addon_id, $is_addon_installed ),
822
+ 'is_whitelabeled' => ( $is_whitelabeled && ! $is_data_debug_mode )
823
+ );
824
+
825
+ fs_require_template(
826
+ 'account/partials/addon.php',
827
+ $addon_view_params
828
+ );
829
+
830
+ $odd = ! $odd;
831
+ } ?>
832
+ </tbody>
833
+ </table>
834
+ </div>
835
+ </div>
836
+ <?php endif ?>
837
+
838
+ <?php $fs->do_action( 'after_account_details' ) ?>
839
+
840
+ <?php
841
+ if ( $show_billing ) {
842
+ $view_params = array( 'id' => $VARS['id'] );
843
+ fs_require_once_template( 'account/billing.php', $view_params );
844
+ fs_require_once_template( 'account/payments.php', $view_params );
845
+ }
846
+ ?>
847
+ </div>
848
+ </div>
849
+ </div>
850
+ </div>
851
+ </div>
852
+ <?php
853
+ $subscription_cancellation_dialog_box_template_params = $fs->_get_subscription_cancellation_dialog_box_template_params( true );
854
+ if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) {
855
+ fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params );
856
+ }
857
+ ?>
858
+ <script type="text/javascript">
859
+ (function ($) {
860
+ var setLoading = function ($this, label) {
861
+ // Set loading mode.
862
+ $(document.body).css({'cursor': 'wait'});
863
+
864
+ $this.css({'cursor': 'wait'});
865
+
866
+ if ($this.is('input'))
867
+ $this.val(label);
868
+ else
869
+ $this.html(label);
870
+
871
+ setTimeout(function () {
872
+ $this.attr('disabled', 'disabled');
873
+ }, 200);
874
+ };
875
+
876
+ $('.fs-toggle-visibility').click(function () {
877
+ var
878
+ $this = $(this),
879
+ $parent = $this.closest('tr'),
880
+ $input = $parent.find('input');
881
+
882
+ $parent.find('code').toggle();
883
+ $input.toggle();
884
+
885
+ if ($input.is(':visible')) {
886
+ $this.html('<?php fs_esc_js_echo_x_inline( 'Hide', 'verb', 'hide', $slug ) ?>');
887
+ setTimeout(function () {
888
+ $input.select().focus();
889
+ }, 100);
890
+ }
891
+ else {
892
+ $this.html( '<?php fs_esc_js_echo_x_inline( 'Show', 'verb', 'show', $slug ) ?>' );
893
+ }
894
+ });
895
+
896
+ $('.fs-toggle-tracking').click(function () {
897
+ setLoading(
898
+ $(this),
899
+ ($(this).data('is-disconnected') ?
900
+ '<?php fs_esc_js_echo_inline('Opting in', 'opting-in' ) ?>' :
901
+ '<?php fs_esc_js_echo_inline('Opting out', 'opting-out' ) ?>') +
902
+ '...'
903
+ );
904
+ });
905
+
906
+ <?php
907
+ $plugin_title = $fs->get_plugin_title();
908
+ $processing_text = fs_esc_js_inline( 'Processing', 'processing' );
909
+ $confirmation_message = sprintf(
910
+ '%s %s',
911
+ sprintf( fs_esc_attr_inline( 'Get updates for bleeding edge Beta versions of %s.', 'get-beta-versions', $slug ), $plugin_title ),
912
+ sprintf( fs_esc_attr_inline( 'An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.', 'beta-version-update-caution', $slug ), $plugin_title )
913
+ );
914
+ ?>
915
+
916
+ $( '.fs-toggle-beta-mode' ).click( function () {
917
+ var $checkbox = $( this ),
918
+ isChecked = $checkbox.is( ':checked' );
919
+
920
+ if ( ! isChecked || confirm( '<?php echo $confirmation_message ?>' ) ) {
921
+ $.ajax( {
922
+ url : ajaxurl,
923
+ method: 'POST',
924
+ data : {
925
+ action : '<?php echo $fs->get_ajax_action( 'set_beta_mode' ) ?>',
926
+ security : '<?php echo $fs->get_ajax_security( 'set_beta_mode' ) ?>',
927
+ is_beta : isChecked,
928
+ module_id: <?php echo $fs->get_id() ?>
929
+ },
930
+ beforeSend: function () {
931
+ $checkbox.prop( 'disabled', true );
932
+ $checkbox.parent().find( 'span' ).text( '<?php echo $processing_text ?>' + '...' );
933
+ },
934
+ complete: function () {
935
+ $checkbox.prop( 'disabled', false );
936
+ $checkbox.parent().find( 'span' ).text( '<?php fs_esc_js_echo_inline( 'Join the Beta Program', 'join-beta', $slug ) ?>' );
937
+ }
938
+ } );
939
+
940
+ return true;
941
+ }
942
+
943
+ return false;
944
+ });
945
+
946
+ $('.fs-opt-in').click(function () {
947
+ setLoading($(this), '<?php fs_esc_js_echo_inline('Opting in', 'opting-in' ) ?>...');
948
+ });
949
+
950
+ $( '#fs_downgrade' ).submit(function( event ) {
951
+ event.preventDefault();
952
+
953
+ setLoading( $( this ).find( '.button' ), '<?php fs_esc_js_echo_inline( 'Downgrading', 'downgrading' ) ?>...' );
954
+ });
955
+
956
+ $('.fs-activate-license').click(function () {
957
+ setLoading($(this), '<?php fs_esc_js_echo_inline('Activating', 'activating' ) ?>...');
958
+ });
959
+
960
+ var $deactivateLicenseOrCancelTrial = $( '.fs-deactivate-license, .fs-cancel-trial' ),
961
+ $subscriptionCancellationModal = $( '.fs-modal-subscription-cancellation-<?php echo $fs->get_id() ?>' );
962
+
963
+ if ( 0 !== $subscriptionCancellationModal.length ) {
964
+ $subscriptionCancellationModal.on( '<?php echo $fs->get_action_tag( 'subscription_cancellation_action' ) ?>', function( evt, cancelSubscription ) {
965
+ setLoading(
966
+ $deactivateLicenseOrCancelTrial,
967
+ ( ! $deactivateLicenseOrCancelTrial.hasClass( 'fs-cancel-trial' ) ?
968
+ '<?php fs_esc_js_echo_inline( 'Deactivating', 'deactivating', $slug ) ?>' :
969
+ '<?php echo esc_html( sprintf( fs_text_inline( 'Cancelling %s', 'cancelling-x', $slug ), fs_text_inline( 'trial', 'trial', $slug ) ) ) ?>' ) + '...'
970
+ );
971
+
972
+ $subscriptionCancellationModal.find( '.fs-modal-footer .button' ).addClass( 'disabled' );
973
+ $deactivateLicenseOrCancelTrial.unbind( 'click' );
974
+
975
+ if ( false === cancelSubscription || $deactivateLicenseOrCancelTrial.hasClass( 'fs-cancel-trial' ) ) {
976
+ $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).text( $deactivateLicenseOrCancelTrial.text() );
977
+
978
+ $deactivateLicenseOrCancelTrial[0].parentNode.submit();
979
+ } else {
980
+ var $form = $( 'input[value="downgrade_account"],input[value="cancel_trial"]' ).parent();
981
+ $form.prepend( '<input type="hidden" name="deactivate_license" value="true" />' );
982
+
983
+ $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).text( '<?php echo esc_js( sprintf(
984
+ fs_text_inline( 'Cancelling %s...', 'cancelling-x' , $slug ),
985
+ $is_paid_trial ?
986
+ fs_text_inline( 'trial', 'trial', $slug ) :
987
+ fs_text_inline( 'subscription', 'subscription', $slug )
988
+ ) ) ?>' );
989
+
990
+ $form.submit();
991
+ }
992
+ });
993
+ }
994
+
995
+ $deactivateLicenseOrCancelTrial.click(function() {
996
+ var $this = $( this );
997
+ if ( $this.hasClass( 'fs-cancel-trial' ) ) {
998
+ $subscriptionCancellationModal.find( '.fs-modal-panel' ).find( 'ul.subscription-actions, .fs-price-increase-warning' ).remove();
999
+ $subscriptionCancellationModal.find( '.fs-modal-panel > p' ).text( <?php echo json_encode( $cancel_trial_confirm_text ) ?> );
1000
+ $subscriptionCancellationModal.trigger( 'showModal' );
1001
+ } else if (confirm('<?php fs_esc_attr_echo_inline( 'Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?', 'deactivate-license-confirm', $slug ) ?>')) {
1002
+ var $this = $(this);
1003
+
1004
+ if ( 0 !== $subscriptionCancellationModal.length ) {
1005
+ $subscriptionCancellationModal.trigger( 'showModal' );
1006
+ } else {
1007
+ setLoading( $this, '<?php fs_esc_js_echo_inline( 'Deactivating', 'deactivating', $slug ) ?>...' );
1008
+ $this[0].parentNode.submit();
1009
+ }
1010
+ }
1011
+
1012
+ return false;
1013
+ });
1014
+
1015
+ var $sitesSection = $('#fs_sites'),
1016
+ $sitesTable = $sitesSection.find('.fs-scrollable-table'),
1017
+ $sitesTableRows = $sitesTable.find('.fs-site-details');
1018
+
1019
+ $('.fs-show-install-details').click(function(){
1020
+ var installID = $(this).parents('.fs-site-details').attr('data-install-id');
1021
+ $sitesSection.find('.fs-install-details[data-install-id=' + installID + ']').toggle();
1022
+ });
1023
+
1024
+
1025
+ var adjustColumnWidth = function($table) {
1026
+ var $headerColumns = $table.find('.fs-table-head td'),
1027
+ $bodyColumns = $table.find('.fs-table-body tr:first > td');
1028
+
1029
+ for (var i = 0, len = $headerColumns.length; i < len; i++) {
1030
+ $($headerColumns[i]).width($($bodyColumns[i]).width());
1031
+ }
1032
+ for (i = 0, len = $headerColumns.length; i < len; i++) {
1033
+ $($bodyColumns[i]).width($($headerColumns[i]).width());
1034
+ }
1035
+ };
1036
+
1037
+ adjustColumnWidth($sitesTable);
1038
+
1039
+ $sitesSection.find('.fs-search').keyup(function(){
1040
+ var search = $(this).val().trim();
1041
+
1042
+ if ('' === search){
1043
+ // Show all.
1044
+ $sitesTableRows.show();
1045
+ return;
1046
+ }
1047
+
1048
+ var url;
1049
+
1050
+ $sitesTableRows.each(function(index){
1051
+ url = $(this).find('.fs-field-url').html();
1052
+
1053
+ if (-1 < url.indexOf(search)){
1054
+ $(this).show();
1055
+ } else {
1056
+ $(this).hide();
1057
+ }
1058
+ });
1059
+ });
1060
+
1061
+ $( '.fs-toggle-whitelabel-mode' ).click( function () {
1062
+ var $toggleLink = $( this );
1063
+
1064
+ $.ajax( {
1065
+ url : ajaxurl,
1066
+ method: 'POST',
1067
+ data : {
1068
+ action : '<?php echo $fs->get_ajax_action( 'toggle_whitelabel_mode' ) ?>',
1069
+ security : '<?php echo $fs->get_ajax_security( 'toggle_whitelabel_mode' ) ?>',
1070
+ module_id: <?php echo $fs->get_id() ?>
1071
+ },
1072
+ beforeSend: function () {
1073
+ $toggleLink.parent().text( '<?php
1074
+ $is_whitelabeled ?
1075
+ fs_esc_html_echo_inline( 'Disabling white-label mode', 'disabling-whitelabel-mode' ) :
1076
+ fs_esc_html_echo_inline( 'Enabling white-label mode', 'enabling-whitelabel-mode' )
1077
+ ?>' + '...' );
1078
+ },
1079
+ complete: function () {
1080
+ location.reload();
1081
+ }
1082
+ } );
1083
+ });
1084
+ })(jQuery);
1085
+ </script>
1086
+ <?php
1087
+ if ( $has_tabs ) {
1088
+ $fs->_add_tabs_after_content();
1089
+ }
1090
+
1091
+ $params = array(
1092
+ 'page' => 'account',
1093
+ 'module_id' => $fs->get_id(),
1094
+ 'module_type' => $fs->get_module_type(),
1095
+ 'module_slug' => $slug,
1096
+ 'module_version' => $fs->get_plugin_version(),
1097
+ );
1098
  fs_require_template( 'powered-by.php', $params );
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/connect.php CHANGED
@@ -1,1038 +1,1038 @@
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.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
- $slug = $fs->get_slug();
19
-
20
- $is_pending_activation = $fs->is_pending_activation();
21
- $is_premium_only = $fs->is_only_premium();
22
- $has_paid_plans = $fs->has_paid_plan();
23
- $is_premium_code = $fs->is_premium();
24
- $is_freemium = $fs->is_freemium();
25
-
26
- $fs->_enqueue_connect_essentials();
27
-
28
- $current_user = Freemius::_get_current_wp_user();
29
-
30
- $first_name = $current_user->user_firstname;
31
- if ( empty( $first_name ) ) {
32
- $first_name = $current_user->nickname;
33
- }
34
-
35
- $site_url = get_site_url();
36
- $protocol_pos = strpos( $site_url, '://' );
37
- if ( false !== $protocol_pos ) {
38
- $site_url = substr( $site_url, $protocol_pos + 3 );
39
- }
40
-
41
- $freemius_site_www = 'https://freemius.com';
42
-
43
- $freemius_usage_tracking_url = $fs->get_usage_tracking_terms_url();
44
- $freemius_plugin_terms_url = $fs->get_eula_url();
45
-
46
- $freemius_site_url = $fs->is_premium() ?
47
- $freemius_site_www :
48
- $freemius_usage_tracking_url;
49
-
50
- if ( $fs->is_premium() ) {
51
- $freemius_site_url .= '?' . http_build_query( array(
52
- 'id' => $fs->get_id(),
53
- 'slug' => $slug,
54
- ) );
55
- }
56
-
57
- $freemius_link = '<a href="' . $freemius_site_url . '" target="_blank" rel="noopener" tabindex="1">freemius.com</a>';
58
-
59
- $error = fs_request_get( 'error' );
60
-
61
- $require_license_key = $is_premium_only ||
62
- ( $is_freemium && $is_premium_code && fs_request_get_bool( 'require_license', true ) );
63
-
64
- if ( $is_pending_activation ) {
65
- $require_license_key = false;
66
- }
67
-
68
- if ( $require_license_key ) {
69
- $fs->_add_license_activation_dialog_box();
70
- }
71
-
72
- $is_optin_dialog = (
73
- $fs->is_theme() &&
74
- $fs->is_themes_page() &&
75
- $fs->show_opt_in_on_themes_page()
76
- );
77
-
78
- if ( $is_optin_dialog ) {
79
- $show_close_button = false;
80
- $previous_theme_activation_url = '';
81
-
82
- if ( ! $is_premium_code ) {
83
- $show_close_button = true;
84
- } else if ( $is_premium_only ) {
85
- $previous_theme_activation_url = $fs->get_previous_theme_activation_url();
86
- $show_close_button = ( ! empty( $previous_theme_activation_url ) );
87
- }
88
- }
89
-
90
- $is_network_level_activation = (
91
- fs_is_network_admin() &&
92
- $fs->is_network_active() &&
93
- ! $fs->is_network_delegated_connection()
94
- );
95
-
96
- $fs_user = Freemius::_get_user_by_email( $current_user->user_email );
97
-
98
- $activate_with_current_user = (
99
- is_object( $fs_user ) &&
100
- ! $is_pending_activation &&
101
- // If requires a license for activation, use the user associated with the license for the opt-in.
102
- ! $require_license_key &&
103
- ! $is_network_level_activation
104
- );
105
-
106
- $optin_params = $fs->get_opt_in_params( array(), $is_network_level_activation );
107
- $sites = isset( $optin_params['sites'] ) ? $optin_params['sites'] : array();
108
-
109
- $is_network_upgrade_mode = ( fs_is_network_admin() && $fs->is_network_upgrade_mode() );
110
-
111
- /* translators: %s: name (e.g. Hey John,) */
112
- $hey_x_text = esc_html( sprintf( fs_text_x_inline( 'Hey %s,', 'greeting', 'hey-x', $slug ), $first_name ) );
113
-
114
- $is_gdpr_required = ( ! $is_pending_activation && ! $require_license_key ) ?
115
- FS_GDPR_Manager::instance()->is_required() :
116
- false;
117
-
118
- if ( is_null( $is_gdpr_required ) ) {
119
- $is_gdpr_required = $fs->fetch_and_store_current_user_gdpr_anonymously();
120
- }
121
- ?>
122
- <?php
123
- if ( $is_optin_dialog ) { ?>
124
- <div id="fs_theme_connect_wrapper">
125
- <?php
126
- if ( $show_close_button ) { ?>
127
- <button class="close dashicons dashicons-no"><span class="screen-reader-text">Close connect dialog</span>
128
- </button>
129
- <?php
130
- }
131
- ?>
132
- <?php
133
- }
134
-
135
- /**
136
- * Allows developers to include custom HTML before the opt-in content.
137
- *
138
- * @author Vova Feldman
139
- * @since 2.3.2
140
- */
141
- $fs->do_action( 'connect/before' );
142
- ?>
143
- <div id="fs_connect"
144
- class="wrap<?php if ( ! fs_is_network_admin() && ( ! $fs->is_enable_anonymous() || $is_pending_activation || $require_license_key ) ) {
145
- echo ' fs-anonymous-disabled';
146
- } ?><?php echo $require_license_key ? ' require-license-key' : '' ?>">
147
- <div class="fs-visual">
148
- <b class="fs-site-icon"><i class="dashicons dashicons-wordpress"></i></b>
149
- <i class="dashicons dashicons-plus fs-first"></i>
150
- <?php
151
- $vars = array( 'id' => $fs->get_id() );
152
- fs_require_once_template( 'plugin-icon.php', $vars );
153
- ?>
154
- <i class="dashicons dashicons-plus fs-second"></i>
155
- <img class="fs-connect-logo" width="80" height="80" src="//img.freemius.com/connect-logo.png"/>
156
- </div>
157
- <div class="fs-content">
158
- <?php if ( ! empty( $error ) ) : ?>
159
- <p class="fs-error"><?php echo esc_html( $error ) ?></p>
160
- <?php endif ?>
161
- <p><?php
162
- $button_label = fs_text_inline( 'Allow & Continue', 'opt-in-connect', $slug );
163
- $message = '';
164
-
165
- if ( $is_pending_activation ) {
166
- $button_label = fs_text_inline( 'Re-send activation email', 'resend-activation-email', $slug );
167
-
168
- $message = $fs->apply_filters( 'pending_activation_message', sprintf(
169
- /* translators: %s: name (e.g. Thanks John!) */
170
- fs_text_inline( 'Thanks %s!', 'thanks-x', $slug ) . '<br>' .
171
- fs_text_inline( '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.', 'pending-activation-message', $slug ),
172
- $first_name,
173
- '<b>' . $fs->get_plugin_name() . '</b>',
174
- '<b>' . $current_user->user_email . '</b>',
175
- fs_text_inline( 'complete the install', 'complete-the-install', $slug )
176
- ) );
177
- } else if ( $require_license_key ) {
178
- $button_label = $is_network_upgrade_mode ?
179
- fs_text_inline( 'Activate License', 'agree-activate-license', $slug ) :
180
- fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug );
181
-
182
- $message = $fs->apply_filters(
183
- 'connect-message_on-premium',
184
- sprintf( fs_text_inline( 'Welcome to %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '<b>' . $fs->get_plugin_name() . '</b>' ),
185
- $first_name,
186
- $fs->get_plugin_name()
187
- );
188
- } else {
189
- $filter = 'connect_message';
190
- $default_optin_message = $is_gdpr_required ?
191
- fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug) :
192
- fs_text_inline( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug);
193
-
194
- if ( $fs->is_plugin_update() ) {
195
- // If Freemius was added on a plugin update, set different
196
- // opt-in message.
197
- $default_optin_message = $is_gdpr_required ?
198
- fs_text_inline( '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.', 'connect-message_on-update', $slug ) :
199
- fs_text_inline( '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.', 'connect-message_on-update', $slug );
200
-
201
- // If user customized the opt-in message on update, use
202
- // that message. Otherwise, fallback to regular opt-in
203
- // custom message if exist.
204
- if ( $fs->has_filter( 'connect_message_on_update' ) ) {
205
- $filter = 'connect_message_on_update';
206
- }
207
- }
208
-
209
- $message = $fs->apply_filters(
210
- $filter,
211
- ($is_network_upgrade_mode ?
212
- '' :
213
- /* translators: %s: name (e.g. Hey John,) */
214
- $hey_x_text . '<br>'
215
- ) .
216
- sprintf(
217
- esc_html( $default_optin_message ),
218
- '<b>' . esc_html( $fs->get_plugin_name() ) . '</b>',
219
- '<b>' . $current_user->user_login . '</b>',
220
- '<a href="' . $site_url . '" target="_blank" rel="noopener noreferrer">' . $site_url . '</a>',
221
- $freemius_link
222
- ),
223
- $first_name,
224
- $fs->get_plugin_name(),
225
- $current_user->user_login,
226
- '<a href="' . $site_url . '" target="_blank" rel="noopener noreferrer">' . $site_url . '</a>',
227
- $freemius_link,
228
- $is_gdpr_required
229
- );
230
- }
231
-
232
- if ( $is_network_upgrade_mode ) {
233
- $network_integration_text = esc_html( fs_text_inline( 'We\'re excited to introduce the Freemius network-level integration.', 'connect_message_network_upgrade', $slug ) );
234
-
235
- if ($is_premium_code){
236
- $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %d site(s) that are still pending license activation.', 'connect_message_network_upgrade-premium', $slug ), count( $sites ) );
237
-
238
- $message .= '<br><br>' . sprintf( fs_text_inline( 'If you\'d like to use the %s on those sites, please enter your license key below and click the activation button.', 'connect_message_network_upgrade-premium-activate-license', $slug ), $is_premium_only ? $fs->get_module_label( true ) : sprintf(
239
- /* translators: %s: module type (plugin, theme, or add-on) */
240
- fs_text_inline( "%s's paid features", 'x-paid-features', $slug ),
241
- $fs->get_module_label( true )
242
- ) );
243
-
244
- /* translators: %s: module type (plugin, theme, or add-on) */
245
- $message .= ' ' . sprintf( fs_text_inline( 'Alternatively, you can skip it for now and activate the license later, in your %s\'s network-level Account page.', 'connect_message_network_upgrade-premium-skip-license', $slug ), $fs->get_module_label( true ) );
246
- }else {
247
- $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %s site(s) in the network that are still pending your attention.', 'connect_message_network_upgrade-free', $slug ), count( $sites ) ) . '<br><br>' . ( fs_starts_with( $message, $hey_x_text . '<br>' ) ? substr( $message, strlen( $hey_x_text . '<br>' ) ) : $message );
248
- }
249
- }
250
-
251
- echo $message;
252
- ?></p>
253
- <?php if ( $require_license_key ) : ?>
254
- <div class="fs-license-key-container">
255
- <input id="fs_license_key" name="fs_key" type="text" required maxlength="<?php echo $fs->apply_filters('license_key_maxlength', 32) ?>"
256
- placeholder="<?php fs_esc_attr_echo_inline( 'License key', 'license-key', $slug ) ?>" tabindex="1"/>
257
- <i class="dashicons dashicons-admin-network"></i>
258
- <a class="show-license-resend-modal show-license-resend-modal-<?php echo $fs->get_unique_affix() ?>"
259
- href="#"><?php fs_esc_html_echo_inline( "Can't find your license key?", 'cant-find-license-key', $slug ); ?></a>
260
- </div>
261
-
262
- <?php
263
- /**
264
- * Allows developers to include custom HTML after the license input container.
265
- *
266
- * @author Vova Feldman
267
- * @since 2.1.2
268
- */
269
- $fs->do_action( 'connect/after_license_input' );
270
- ?>
271
-
272
- <?php
273
- $send_updates_text = sprintf(
274
- '%s<span class="action-description"> - %s</span>',
275
- $fs->get_text_inline( 'Yes', 'yes' ),
276
- $fs->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' )
277
- );
278
-
279
- $do_not_send_updates_text = sprintf(
280
- '%s<span class="action-description"> - %s</span>',
281
- $fs->get_text_inline( 'No', 'no' ),
282
- sprintf(
283
- $fs->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ),
284
- '<span class="underlined">',
285
- '</span>'
286
- )
287
- );
288
- ?>
289
- <div id="fs_marketing_optin">
290
- <span class="fs-message"><?php fs_echo_inline( "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:", 'contact-for-updates' ) ?></span>
291
- <div class="fs-input-container">
292
- <label>
293
- <input type="radio" name="allow-marketing" value="true" tabindex="1" />
294
- <span class="fs-input-label"><?php echo $send_updates_text ?></span>
295
- </label>
296
- <label>
297
- <input type="radio" name="allow-marketing" value="false" tabindex="1" />
298
- <span class="fs-input-label"><?php echo $do_not_send_updates_text ?></span>
299
- </label>
300
- </div>
301
- </div>
302
- <?php endif ?>
303
- <?php if ( $is_network_level_activation ) : ?>
304
- <?php
305
- $vars = array(
306
- 'id' => $fs->get_id(),
307
- 'sites' => $sites,
308
- 'require_license_key' => $require_license_key
309
- );
310
-
311
- echo fs_get_template( 'partials/network-activation.php', $vars );
312
- ?>
313
- <?php endif ?>
314
- </div>
315
- <div class="fs-actions">
316
- <?php if ( $fs->is_enable_anonymous() && ! $is_pending_activation && ( ! $require_license_key || $is_network_upgrade_mode ) ) : ?>
317
- <a id="skip_activation" href="<?php echo fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_skip_activation' ), $is_network_level_activation ), $fs->get_unique_affix() . '_skip_activation' ) ?>"
318
- class="button button-secondary" tabindex="2"><?php fs_esc_html_echo_x_inline( 'Skip', 'verb', 'skip', $slug ) ?></a>
319
- <?php endif ?>
320
- <?php if ( $is_network_level_activation && $fs->apply_filters( 'show_delegation_option', true ) ) : ?>
321
- <a id="delegate_to_site_admins" class="fs-tooltip-trigger <?php echo is_rtl() ? ' rtl' : '' ?>" href="<?php echo fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_delegate_activation' ) ), $fs->get_unique_affix() . '_delegate_activation' ) ?>"><?php fs_esc_html_echo_inline( 'Delegate to Site Admins', 'delegate-to-site-admins', $slug ) ?><span class="fs-tooltip"><?php fs_esc_html_echo_inline( 'If you click it, this decision will be delegated to the sites administrators.', 'delegate-sites-tooltip', $slug ) ?></span></a>
322
- <?php endif ?>
323
- <?php if ( $activate_with_current_user ) : ?>
324
- <form action="" method="POST">
325
- <input type="hidden" name="fs_action"
326
- value="<?php echo $fs->get_unique_affix() ?>_activate_existing">
327
- <?php wp_nonce_field( 'activate_existing_' . $fs->get_public_key() ) ?>
328
- <input type="hidden" name="is_extensions_tracking_allowed" value="1">
329
- <button class="button button-primary" tabindex="1"
330
- type="submit"><?php echo esc_html( $button_label ) ?></button>
331
- </form>
332
- <?php else : ?>
333
- <form method="post" action="<?php echo WP_FS__ADDRESS ?>/action/service/user/install/">
334
- <?php unset( $optin_params['sites']); ?>
335
- <?php foreach ( $optin_params as $name => $value ) : ?>
336
- <input type="hidden" name="<?php echo $name ?>" value="<?php echo esc_attr( $value ) ?>">
337
- <?php endforeach ?>
338
- <input type="hidden" name="is_extensions_tracking_allowed" value="1">
339
- <button class="button button-primary" tabindex="1"
340
- type="submit"<?php if ( $require_license_key ) {
341
- echo ' disabled="disabled"';
342
- } ?>><?php echo esc_html( $button_label ) ?></button>
343
- </form>
344
- <?php endif ?>
345
- <?php if ( $require_license_key ) : ?>
346
- <a id="license_issues_link" href="<?php echo $fs->apply_filters( 'known_license_issues_url', 'https://freemius.com/help/documentation/wordpress-sdk/license-activation-issues/' ) ?>" target="_blank"><?php fs_esc_html_echo_inline( 'License issues?', 'license-issues', $slug ) ?></a>
347
- <?php endif ?>
348
- </div><?php
349
-
350
- // Set core permission list items.
351
- $permissions = array();
352
-
353
- /**
354
- * When activating a license key the information of the admin is not collected, we gather the user info from the license.
355
- *
356
- * @since 2.3.2
357
- * @author Vova Feldman
358
- */
359
- if ( ! $require_license_key ) {
360
- $permissions['profile'] = array(
361
- 'icon-class' => 'dashicons dashicons-admin-users',
362
- 'label' => $fs->get_text_inline( 'Your Profile Overview', 'permissions-profile' ),
363
- 'desc' => $fs->get_text_inline( 'Name and email address', 'permissions-profile_desc' ),
364
- 'priority' => 5,
365
- );
366
- }
367
-
368
- $permissions['site'] = array(
369
- 'icon-class' => 'dashicons dashicons-admin-settings',
370
- 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can manage and control your license remotely from the User Dashboard.', 'permissions-site_tooltip' ), $fs->get_module_type() ) : '' ),
371
- 'label' => $fs->get_text_inline( 'Your Site Overview', 'permissions-site' ),
372
- 'desc' => $fs->get_text_inline( 'Site URL, WP version, PHP info', 'permissions-site_desc' ),
373
- 'priority' => 10,
374
- );
375
-
376
- if ( ! $require_license_key ) {
377
- $permissions['notices'] = array(
378
- 'icon-class' => 'dashicons dashicons-testimonial',
379
- 'label' => $fs->get_text_inline( 'Admin Notices', 'permissions-admin-notices' ),
380
- 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ),
381
- 'priority' => 13,
382
- );
383
- }
384
-
385
- $permissions['events'] = array(
386
- 'icon-class' => 'dashicons dashicons-admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ),
387
- 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can reuse the license when the %s is no longer active.', 'permissions-events_tooltip' ), $fs->get_module_type() ) : '' ),
388
- 'label' => sprintf( $fs->get_text_inline( 'Current %s Status', 'permissions-events' ), ucfirst( $fs->get_module_type() ) ),
389
- 'desc' => $fs->get_text_inline( 'Active, deactivated, or uninstalled', 'permissions-events_desc' ),
390
- 'priority' => 20,
391
- );
392
-
393
- // Add newsletter permissions if enabled.
394
- if ( $is_gdpr_required || $fs->is_permission_requested( 'newsletter' ) ) {
395
- $permissions['newsletter'] = array(
396
- 'icon-class' => 'dashicons dashicons-email-alt',
397
- 'label' => $fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ),
398
- 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ),
399
- 'priority' => 15,
400
- );
401
- }
402
-
403
- $permissions['extensions'] = array(
404
- 'icon-class' => 'dashicons dashicons-menu',
405
- 'label' => $fs->get_text_inline( 'Plugins & Themes', 'permissions-extensions' ) . ( $require_license_key ? ' (' . $fs->get_text_inline( 'optional' ) . ')' : '' ),
406
- 'tooltip' => $fs->get_text_inline( 'To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts.', 'permissions-events_tooltip' ),
407
- 'desc' => $fs->get_text_inline( 'Title, slug, version, and is active', 'permissions-extensions_desc' ),
408
- 'priority' => 25,
409
- 'optional' => true,
410
- 'default' => $fs->apply_filters( 'permission_extensions_default', ! $require_license_key )
411
- );
412
-
413
- // Allow filtering of the permissions list.
414
- $permissions = $fs->apply_filters( 'permission_list', $permissions );
415
-
416
- // Sort by priority.
417
- uasort( $permissions, 'fs_sort_by_priority' );
418
-
419
- if ( ! empty( $permissions ) ) : ?>
420
- <div class="fs-permissions">
421
- <?php if ( $require_license_key ) : ?>
422
- <p class="fs-license-sync-disclaimer"><?php
423
- echo sprintf(
424
- fs_esc_html_inline( 'The %1$s will periodically send %2$s to %3$s for security & feature updates delivery, and license management.', 'license-sync-disclaimer', $slug ),
425
- $fs->get_module_label( true ),
426
- sprintf('<a class="fs-trigger" href="#" tabindex="1">%s</a>', fs_esc_html_inline('diagnostic data', 'send-data')),
427
- '<a class="fs-tooltip-trigger' . (is_rtl() ? ' rtl' : '') . '" href="' . $freemius_site_url . '" target="_blank" rel="noopener" tabindex="1">freemius.com <i class="dashicons dashicons-editor-help" style="text-decoration: none;"><span class="fs-tooltip" style="width: 170px">' . $fs->get_text_inline( 'Freemius is our licensing and software updates engine', 'permissions-extensions_desc' ) . '</span></i></a>'
428
- ) ?></p>
429
- <?php else : ?>
430
- <a class="fs-trigger" href="#" tabindex="1"><?php fs_esc_html_echo_inline( 'What permissions are being granted?', 'what-permissions', $slug ) ?></a>
431
- <?php endif ?>
432
- <ul><?php
433
- foreach ( $permissions as $id => $permission ) : ?>
434
- <li id="fs-permission-<?php echo esc_attr( $id ); ?>"
435
- class="fs-permission fs-<?php echo esc_attr( $id ); ?>">
436
- <i class="<?php echo esc_attr( $permission['icon-class'] ); ?>"></i>
437
- <?php if ( isset( $permission['optional'] ) && true === $permission['optional'] ) : ?>
438
- <div class="fs-switch fs-small fs-round fs-<?php echo (! isset( $permission['default'] ) || true === $permission['default'] ) ? 'on' : 'off' ?>">
439
- <div class="fs-toggle"></div>
440
- </div>
441
- <?php endif ?>
442
-
443
- <div class="fs-permission-description">
444
- <span<?php if ( ! empty($permission['tooltip']) ) : ?> class="fs-tooltip-trigger"<?php endif ?>><?php echo esc_html( $permission['label'] ); ?><?php if ( ! empty($permission['tooltip']) ) : ?><i class="dashicons dashicons-editor-help"><span class="fs-tooltip" style="width: 200px"><?php echo $permission['tooltip'] ?></span></i><?php endif ?></span>
445
-
446
- <p><?php echo esc_html( $permission['desc'] ); ?></p>
447
- </div>
448
- </li>
449
- <?php endforeach; ?>
450
- </ul>
451
- </div>
452
- <?php endif ?>
453
- <?php if ( $is_premium_code && $is_freemium ) : ?>
454
- <div class="fs-freemium-licensing">
455
- <p>
456
- <?php if ( $require_license_key ) : ?>
457
- <?php fs_esc_html_echo_inline( 'Don\'t have a license key?', 'dont-have-license-key', $slug ) ?>
458
- <a data-require-license="false" tabindex="1"><?php fs_esc_html_echo_inline( 'Activate Free Version', 'activate-free-version', $slug ) ?></a>
459
- <?php else : ?>
460
- <?php fs_echo_inline( 'Have a license key?', 'have-license-key', $slug ) ?>
461
- <a data-require-license="true" tabindex="1"><?php fs_esc_html_echo_inline( 'Activate License', 'activate-license', $slug ) ?></a>
462
- <?php endif ?>
463
- </p>
464
- </div>
465
- <?php endif ?>
466
- <div class="fs-terms">
467
- <a href="https://freemius.com/privacy/" target="_blank" rel="noopener"
468
- tabindex="1"><?php fs_esc_html_echo_inline( 'Privacy Policy', 'privacy-policy', $slug ) ?></a>
469
- &nbsp;&nbsp;-&nbsp;&nbsp;
470
- <a href="<?php echo $require_license_key ? $freemius_plugin_terms_url : $freemius_usage_tracking_url ?>" target="_blank" rel="noopener" tabindex="1"><?php $require_license_key ? fs_echo_inline( 'License Agreement', 'license-agreement', $slug ) : fs_echo_inline( 'Terms of Service', 'tos', $slug ) ?></a>
471
- </div>
472
- </div>
473
- <?php
474
- /**
475
- * Allows developers to include custom HTML after the opt-in content.
476
- *
477
- * @author Vova Feldman
478
- * @since 2.3.2
479
- */
480
- $fs->do_action( 'connect/after' );
481
-
482
- if ( $is_optin_dialog ) { ?>
483
- </div>
484
- <?php
485
- }
486
- ?>
487
- <script type="text/javascript">
488
- (function ($) {
489
- var $html = $('html');
490
-
491
- <?php
492
- if ( $is_optin_dialog ) {
493
- if ( $show_close_button ) { ?>
494
- var $themeConnectWrapper = $('#fs_theme_connect_wrapper');
495
-
496
- $themeConnectWrapper.find('button.close').on('click', function () {
497
- <?php if ( ! empty( $previous_theme_activation_url ) ) { ?>
498
- location.href = '<?php echo html_entity_decode( $previous_theme_activation_url ); ?>';
499
- <?php } else { ?>
500
- $themeConnectWrapper.remove();
501
- $html.css({overflow: $html.attr('fs-optin-overflow')});
502
- <?php } ?>
503
- });
504
- <?php
505
- }
506
- ?>
507
-
508
- $html.attr('fs-optin-overflow', $html.css('overflow'));
509
- $html.css({overflow: 'hidden'});
510
-
511
- <?php
512
- }
513
- ?>
514
-
515
- var $primaryCta = $('.fs-actions .button.button-primary'),
516
- primaryCtaLabel = $primaryCta.html(),
517
- $form = $('.fs-actions form'),
518
- isNetworkActive = <?php echo $is_network_level_activation ? 'true' : 'false' ?>,
519
- requireLicenseKey = <?php echo $require_license_key ? 'true' : 'false' ?>,
520
- hasContextUser = <?php echo $activate_with_current_user ? 'true' : 'false' ?>,
521
- isNetworkUpgradeMode = <?php echo $is_network_upgrade_mode ? 'true' : 'false' ?>,
522
- $licenseSecret,
523
- $licenseKeyInput = $('#fs_license_key'),
524
- pauseCtaLabelUpdate = false,
525
- isNetworkDelegating = false,
526
- /**
527
- * @author Leo Fajardo (@leorw)
528
- * @since 2.1.0
529
- */
530
- resetLoadingMode = function() {
531
- // Reset loading mode.
532
- $primaryCta.html(primaryCtaLabel);
533
- $primaryCta.prop('disabled', false);
534
- $(document.body).css({'cursor': 'auto'});
535
- $('.fs-loading').removeClass('fs-loading');
536
-
537
- console.log('resetLoadingMode - Primary button was enabled');
538
- },
539
- setLoadingMode = function () {
540
- $(document.body).css({'cursor': 'wait'});
541
- };
542
-
543
- $('.fs-actions .button').on('click', function () {
544
- setLoadingMode();
545
-
546
- var $this = $(this);
547
-
548
- setTimeout(function () {
549
- if ( ! requireLicenseKey || ! $marketingOptin.hasClass( 'error' ) ) {
550
- $this.attr('disabled', 'disabled');
551
- }
552
- }, 200);
553
- });
554
-
555
- if ( isNetworkActive ) {
556
- var
557
- $multisiteOptionsContainer = $( '.fs-multisite-options-container' ),
558
- $allSitesOptions = $( '.fs-all-sites-options' ),
559
- $applyOnAllSites = $( '.fs-apply-on-all-sites-checkbox' ),
560
- $sitesListContainer = $( '.fs-sites-list-container' ),
561
- totalSites = <?php echo count( $sites ) ?>,
562
- maxSitesListHeight = null,
563
- $skipActivationButton = $( '#skip_activation' ),
564
- $delegateToSiteAdminsButton = $( '#delegate_to_site_admins' ),
565
- hasAnyInstall = <?php echo ! is_null( $fs->find_first_install() ) ? 'true' : 'false' ?>;
566
-
567
- $applyOnAllSites.click(function() {
568
- var isChecked = $( this ).is( ':checked' );
569
-
570
- if ( isChecked ) {
571
- $multisiteOptionsContainer.find( '.action' ).removeClass( 'selected' );
572
- updatePrimaryCtaText( 'allow' );
573
- }
574
-
575
- $multisiteOptionsContainer.find( '.action-allow' ).addClass( 'selected' );
576
-
577
- $skipActivationButton.toggle();
578
-
579
- $delegateToSiteAdminsButton.toggle();
580
-
581
- $multisiteOptionsContainer.toggleClass( 'fs-apply-on-all-sites', isChecked );
582
-
583
- $sitesListContainer.toggle( ! isChecked );
584
- if ( ! isChecked && null === maxSitesListHeight ) {
585
- /**
586
- * Set the visible number of rows to 5 (5 * height of the first row).
587
- *
588
- * @author Leo Fajardo (@leorw)
589
- */
590
- maxSitesListHeight = ( 5 * $sitesListContainer.find( 'tr:first' ).height() );
591
- $sitesListContainer.css( 'max-height', maxSitesListHeight );
592
- }
593
- });
594
-
595
- $allSitesOptions.find( '.action' ).click(function( evt ) {
596
- var actionType = $( evt.target ).data( 'action-type' );
597
-
598
- $multisiteOptionsContainer.find( '.action' ).removeClass( 'selected' );
599
- $multisiteOptionsContainer.find( '.action-' + actionType ).toggleClass( 'selected' );
600
-
601
- updatePrimaryCtaText( actionType );
602
- });
603
-
604
- $sitesListContainer.delegate( '.action', 'click', function( evt ) {
605
- var $this = $( evt.target );
606
- if ( $this.hasClass( 'selected' ) ) {
607
- return false;
608
- }
609
-
610
- $this.parents( 'tr:first' ).find( '.action' ).removeClass( 'selected' );
611
- $this.toggleClass( 'selected' );
612
-
613
- var
614
- singleSiteActionType = $this.data( 'action-type' ),
615
- totalSelected = $sitesListContainer.find( '.action-' + singleSiteActionType + '.selected' ).length;
616
-
617
- $allSitesOptions.find( '.action.selected' ).removeClass( 'selected' );
618
-
619
- if ( totalSelected === totalSites ) {
620
- $allSitesOptions.find( '.action-' + singleSiteActionType ).addClass( 'selected' );
621
-
622
- updatePrimaryCtaText( singleSiteActionType );
623
- } else {
624
- updatePrimaryCtaText( 'mixed' );
625
- }
626
- });
627
-
628
- if ( isNetworkUpgradeMode || hasAnyInstall ) {
629
- $skipActivationButton.click(function(){
630
- $delegateToSiteAdminsButton.hide();
631
-
632
- $skipActivationButton.html('<?php fs_esc_js_echo_inline( 'Skipping, please wait', 'skipping-wait', $slug ) ?>...');
633
-
634
- pauseCtaLabelUpdate = true;
635
-
636
- // Check all sites to be skipped.
637
- $allSitesOptions.find('.action.action-skip').click();
638
-
639
- $form.submit();
640
-
641
- pauseCtaLabelUpdate = false;
642
-
643
- return false;
644
- });
645
-
646
- $delegateToSiteAdminsButton.click(function(){
647
- $delegateToSiteAdminsButton.html('<?php fs_esc_js_echo_inline( 'Delegating, please wait', 'delegating-wait', $slug ) ?>...');
648
-
649
- pauseCtaLabelUpdate = true;
650
-
651
- /**
652
- * Set to true so that the form submission handler can differentiate delegation from license
653
- * activation and the proper AJAX action will be used (when delegating, the action should be
654
- * `network_activate` and not `activate_license`).
655
- *
656
- * @author Leo Fajardo (@leorw)
657
- * @since 2.3.0
658
- */
659
- isNetworkDelegating = true;
660
-
661
- // Check all sites to be skipped.
662
- $allSitesOptions.find('.action.action-delegate').click();
663
-
664
- $form.submit();
665
-
666
- pauseCtaLabelUpdate = false;
667
-
668
- /**
669
- * Set to false so that in case the previous AJAX request has failed, the form submission handler
670
- * can differentiate license activation from delegation and the proper AJAX action will be used
671
- * (when activating a license, the action should be `activate_license` and not `network_activate`).
672
- *
673
- * @author Leo Fajardo (@leorw)
674
- * @since 2.3.0
675
- */
676
- isNetworkDelegating = false;
677
-
678
- return false;
679
- });
680
- }
681
- }
682
-
683
- /**
684
- * @author Leo Fajardo (@leorw)
685
- */
686
- function updatePrimaryCtaText( actionType ) {
687
- if (pauseCtaLabelUpdate)
688
- return;
689
-
690
- var text = '<?php fs_esc_js_echo_inline( 'Continue', 'continue', $slug ) ?>';
691
-
692
- switch ( actionType ) {
693
- case 'allow':
694
- text = '<?php fs_esc_js_echo_inline( 'Allow & Continue', 'opt-in-connect', $slug ) ?>';
695
- break;
696
- case 'delegate':
697
- text = '<?php fs_esc_js_echo_inline( 'Delegate to Site Admins & Continue', 'delegate-to-site-admins-and-continue', $slug ) ?>';
698
- break;
699
- case 'skip':
700
- text = '<?php fs_esc_js_echo_x_inline( 'Skip', 'verb', 'skip', $slug ) ?>';
701
- break;
702
- }
703
-
704
- $primaryCta.html( text );
705
- }
706
-
707
- var ajaxOptin = ( requireLicenseKey || isNetworkActive );
708
-
709
- $form.on('submit', function () {
710
- var $extensionsPermission = $('#fs-permission-extensions .fs-switch'),
711
- isExtensionsTrackingAllowed = ($extensionsPermission.length > 0) ?
712
- $extensionsPermission.hasClass('fs-on') :
713
- null;
714
-
715
- if (null === isExtensionsTrackingAllowed) {
716
- $('input[name=is_extensions_tracking_allowed]').remove();
717
- } else {
718
- $('input[name=is_extensions_tracking_allowed]').val(isExtensionsTrackingAllowed ? 1 : 0);
719
- }
720
-
721
- /**
722
- * @author Vova Feldman (@svovaf)
723
- * @since 1.1.9
724
- */
725
- if ( ajaxOptin ) {
726
- if (!hasContextUser || isNetworkUpgradeMode) {
727
- var action = null,
728
- security = null;
729
-
730
- if ( requireLicenseKey && ! isNetworkDelegating ) {
731
- action = '<?php echo $fs->get_ajax_action( 'activate_license' ) ?>';
732
- security = '<?php echo $fs->get_ajax_security( 'activate_license' ) ?>';
733
- } else {
734
- action = '<?php echo $fs->get_ajax_action( 'network_activate' ) ?>';
735
- security = '<?php echo $fs->get_ajax_security( 'network_activate' ) ?>';
736
- }
737
-
738
- $('.fs-error').remove();
739
-
740
- var
741
- licenseKey = $licenseKeyInput.val(),
742
- data = {
743
- action : action,
744
- security : security,
745
- license_key: licenseKey,
746
- module_id : '<?php echo $fs->get_id() ?>'
747
- };
748
-
749
- if (
750
- requireLicenseKey &&
751
- ! isNetworkDelegating &&
752
- isMarketingAllowedByLicense.hasOwnProperty(licenseKey)
753
- ) {
754
- var
755
- isMarketingAllowed = null,
756
- $isMarketingAllowed = $marketingOptin.find( 'input[type="radio"][name="allow-marketing"]:checked');
757
-
758
-
759
- if ($isMarketingAllowed.length > 0)
760
- isMarketingAllowed = ('true' == $isMarketingAllowed.val());
761
-
762
- if ( null == isMarketingAllowedByLicense[ licenseKey ] &&
763
- null == isMarketingAllowed
764
- ) {
765
- $marketingOptin.addClass( 'error' ).show();
766
- resetLoadingMode();
767
- return false;
768
- } else if ( null == isMarketingAllowed ) {
769
- isMarketingAllowed = isMarketingAllowedByLicense[ licenseKey ];
770
- }
771
-
772
- data.is_marketing_allowed = isMarketingAllowed;
773
-
774
- data.is_extensions_tracking_allowed = isExtensionsTrackingAllowed;
775
- }
776
-
777
- $marketingOptin.removeClass( 'error' );
778
-
779
- if ( isNetworkActive ) {
780
- var
781
- sites = [],
782
- applyOnAllSites = $applyOnAllSites.is( ':checked' );
783
-
784
- $sitesListContainer.find( 'tr' ).each(function() {
785
- var
786
- $this = $( this ),
787
- includeSite = ( ! requireLicenseKey || applyOnAllSites || $this.find( 'input' ).is( ':checked' ) );
788
-
789
- if ( ! includeSite )
790
- return;
791
-
792
- var site = {
793
- uid : $this.find( '.uid' ).val(),
794
- url : $this.find( '.url' ).val(),
795
- title : $this.find( '.title' ).val(),
796
- language: $this.find( '.language' ).val(),
797
- charset : $this.find( '.charset' ).val(),
798
- blog_id : $this.find( '.blog-id' ).find( 'span' ).text()
799
- };
800
-
801
- if ( ! requireLicenseKey) {
802
- site.action = $this.find('.action.selected').data('action-type');
803
- } else if ( isNetworkDelegating ) {
804
- site.action = 'delegate';
805
- }
806
-
807
- sites.push( site );
808
- });
809
-
810
- data.sites = sites;
811
-
812
- if ( hasAnyInstall ) {
813
- data.has_any_install = hasAnyInstall;
814
- }
815
- }
816
-
817
- /**
818
- * Use the AJAX opt-in when license key is required to potentially
819
- * process the after install failure hook.
820
- *
821
- * @author Vova Feldman (@svovaf)
822
- * @since 1.2.1.5
823
- */
824
- $.ajax({
825
- url : ajaxurl,
826
- method : 'POST',
827
- data : data,
828
- success: function (result) {
829
- var resultObj = $.parseJSON(result);
830
- if (resultObj.success) {
831
- // Redirect to the "Account" page and sync the license.
832
- window.location.href = resultObj.next_page;
833
- } else {
834
- resetLoadingMode();
835
-
836
- // Show error.
837
- $('.fs-content').prepend('<p class="fs-error">' + (resultObj.error.message ? resultObj.error.message : resultObj.error) + '</p>');
838
- }
839
- },
840
- error: function () {
841
- resetLoadingMode();
842
- }
843
- });
844
-
845
- return false;
846
- }
847
- else {
848
- if (null == $licenseSecret) {
849
- $licenseSecret = $('<input type="hidden" name="license_secret_key" value="" />');
850
- $form.append($licenseSecret);
851
- }
852
-
853
- // Update secret key if premium only plugin.
854
- $licenseSecret.val($licenseKeyInput.val());
855
- }
856
- }
857
-
858
- return true;
859
- });
860
-
861
- $primaryCta.on('click', function () {
862
- console.log('Primary button was clicked');
863
-
864
- $(this).addClass('fs-loading');
865
- $(this).html('<?php echo esc_js( $is_pending_activation ?
866
- fs_text_x_inline( 'Sending email', 'as in the process of sending an email', 'sending-email', $slug ) :
867
- fs_text_x_inline( 'Activating', 'as activating plugin', 'activating', $slug )
868
- ) ?>...');
869
- });
870
-
871
- $('.fs-permissions .fs-trigger').on('click', function () {
872
- $('.fs-permissions').toggleClass('fs-open');
873
-
874
- return false;
875
- });
876
-
877
- $( '.fs-switch' ).click( function () {
878
- $(this)
879
- .toggleClass( 'fs-on' )
880
- .toggleClass( 'fs-off' );
881
- });
882
-
883
- if (requireLicenseKey) {
884
- /**
885
- * Submit license key on enter.
886
- *
887
- * @author Vova Feldman (@svovaf)
888
- * @since 1.1.9
889
- */
890
- $licenseKeyInput.keypress(function (e) {
891
- if (e.which == 13) {
892
- if ('' !== $(this).val()) {
893
- $primaryCta.click();
894
- return false;
895
- }
896
- }
897
- });
898
-
899
- /**
900
- * Disable activation button when empty license key.
901
- *
902
- * @author Vova Feldman (@svovaf)
903
- * @since 1.1.9
904
- */
905
- $licenseKeyInput.on('keyup paste delete cut', function () {
906
- setTimeout(function () {
907
- var key = $licenseKeyInput.val();
908
-
909
- if (key == previousLicenseKey){
910
- return;
911
- }
912
-
913
- if ('' === key) {
914
- $primaryCta.attr('disabled', 'disabled');
915
- $marketingOptin.hide();
916
- } else {
917
- $primaryCta.prop('disabled', false);
918
-
919
- if (32 <= key.length){
920
- fetchIsMarketingAllowedFlagAndToggleOptin();
921
- } else {
922
- $marketingOptin.hide();
923
- }
924
- }
925
-
926
- previousLicenseKey = key;
927
- }, 100);
928
- }).focus();
929
- }
930
-
931
- /**
932
- * Set license mode trigger URL.
933
- *
934
- * @author Vova Feldman (@svovaf)
935
- * @since 1.1.9
936
- */
937
- var
938
- $connectLicenseModeTrigger = $('#fs_connect .fs-freemium-licensing a'),
939
- href = window.location.href;
940
-
941
- if (href.indexOf('?') > 0) {
942
- href += '&';
943
- } else {
944
- href += '?';
945
- }
946
-
947
- if ($connectLicenseModeTrigger.length > 0) {
948
- $connectLicenseModeTrigger.attr(
949
- 'href',
950
- href + 'require_license=' + $connectLicenseModeTrigger.attr('data-require-license')
951
- );
952
- }
953
-
954
- //--------------------------------------------------------------------------------
955
- //region GDPR
956
- //--------------------------------------------------------------------------------
957
- var isMarketingAllowedByLicense = {},
958
- $marketingOptin = $('#fs_marketing_optin'),
959
- previousLicenseKey = null;
960
-
961
- if (requireLicenseKey) {
962
-
963
- var
964
- afterMarketingFlagLoaded = function () {
965
- var licenseKey = $licenseKeyInput.val();
966
-
967
- if (null == isMarketingAllowedByLicense[licenseKey]) {
968
- $marketingOptin.show();
969
-
970
- if ($marketingOptin.find('input[type=radio]:checked').length > 0){
971
- // Focus on button if GDPR opt-in already selected is already selected.
972
- $primaryCta.focus();
973
- } else {
974
- // Focus on the GDPR opt-in radio button.
975
- $($marketingOptin.find('input[type=radio]')[0]).focus();
976
- }
977
- } else {
978
- $marketingOptin.hide();
979
- $primaryCta.focus();
980
- }
981
- },
982
- /**
983
- * @author Leo Fajardo (@leorw)
984
- * @since 2.1.0
985
- */
986
- fetchIsMarketingAllowedFlagAndToggleOptin = function () {
987
- var licenseKey = $licenseKeyInput.val();
988
-
989
- if (licenseKey.length < 32) {
990
- $marketingOptin.hide();
991
- return;
992
- }
993
-
994
- if (isMarketingAllowedByLicense.hasOwnProperty(licenseKey)) {
995
- afterMarketingFlagLoaded();
996
- return;
997
- }
998
-
999
- $marketingOptin.hide();
1000
-
1001
- setLoadingMode();
1002
-
1003
- $primaryCta.addClass('fs-loading');
1004
- $primaryCta.attr('disabled', 'disabled');
1005
- $primaryCta.html('<?php fs_esc_js_echo_inline( 'Please wait', 'please-wait', $slug ) ?>...');
1006
-
1007
- $.ajax({
1008
- url : ajaxurl,
1009
- method : 'POST',
1010
- data : {
1011
- action : '<?php echo $fs->get_ajax_action( 'fetch_is_marketing_required_flag_value' ) ?>',
1012
- security : '<?php echo $fs->get_ajax_security( 'fetch_is_marketing_required_flag_value' ) ?>',
1013
- license_key: licenseKey,
1014
- module_id : '<?php echo $fs->get_id() ?>'
1015
- },
1016
- success: function (result) {
1017
- resetLoadingMode();
1018
-
1019
- if (result.success) {
1020
- result = result.data;
1021
-
1022
- // Cache result.
1023
- isMarketingAllowedByLicense[licenseKey] = result.is_marketing_allowed;
1024
- }
1025
-
1026
- afterMarketingFlagLoaded();
1027
- }
1028
- });
1029
- };
1030
-
1031
- $marketingOptin.find( 'input' ).click(function() {
1032
- $marketingOptin.removeClass( 'error' );
1033
- });
1034
- }
1035
-
1036
- //endregion
1037
- })(jQuery);
1038
  </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.0.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
+ $slug = $fs->get_slug();
19
+
20
+ $is_pending_activation = $fs->is_pending_activation();
21
+ $is_premium_only = $fs->is_only_premium();
22
+ $has_paid_plans = $fs->has_paid_plan();
23
+ $is_premium_code = $fs->is_premium();
24
+ $is_freemium = $fs->is_freemium();
25
+
26
+ $fs->_enqueue_connect_essentials();
27
+
28
+ $current_user = Freemius::_get_current_wp_user();
29
+
30
+ $first_name = $current_user->user_firstname;
31
+ if ( empty( $first_name ) ) {
32
+ $first_name = $current_user->nickname;
33
+ }
34
+
35
+ $site_url = get_site_url();
36
+ $protocol_pos = strpos( $site_url, '://' );
37
+ if ( false !== $protocol_pos ) {
38
+ $site_url = substr( $site_url, $protocol_pos + 3 );
39
+ }
40
+
41
+ $freemius_site_www = 'https://freemius.com';
42
+
43
+ $freemius_usage_tracking_url = $fs->get_usage_tracking_terms_url();
44
+ $freemius_plugin_terms_url = $fs->get_eula_url();
45
+
46
+ $freemius_site_url = $fs->is_premium() ?
47
+ $freemius_site_www :
48
+ $freemius_usage_tracking_url;
49
+
50
+ if ( $fs->is_premium() ) {
51
+ $freemius_site_url .= '?' . http_build_query( array(
52
+ 'id' => $fs->get_id(),
53
+ 'slug' => $slug,
54
+ ) );
55
+ }
56
+
57
+ $freemius_link = '<a href="' . $freemius_site_url . '" target="_blank" rel="noopener" tabindex="1">freemius.com</a>';
58
+
59
+ $error = fs_request_get( 'error' );
60
+
61
+ $require_license_key = $is_premium_only ||
62
+ ( $is_freemium && $is_premium_code && fs_request_get_bool( 'require_license', true ) );
63
+
64
+ if ( $is_pending_activation ) {
65
+ $require_license_key = false;
66
+ }
67
+
68
+ if ( $require_license_key ) {
69
+ $fs->_add_license_activation_dialog_box();
70
+ }
71
+
72
+ $is_optin_dialog = (
73
+ $fs->is_theme() &&
74
+ $fs->is_themes_page() &&
75
+ $fs->show_opt_in_on_themes_page()
76
+ );
77
+
78
+ if ( $is_optin_dialog ) {
79
+ $show_close_button = false;
80
+ $previous_theme_activation_url = '';
81
+
82
+ if ( ! $is_premium_code ) {
83
+ $show_close_button = true;
84
+ } else if ( $is_premium_only ) {
85
+ $previous_theme_activation_url = $fs->get_previous_theme_activation_url();
86
+ $show_close_button = ( ! empty( $previous_theme_activation_url ) );
87
+ }
88
+ }
89
+
90
+ $is_network_level_activation = (
91
+ fs_is_network_admin() &&
92
+ $fs->is_network_active() &&
93
+ ! $fs->is_network_delegated_connection()
94
+ );
95
+
96
+ $fs_user = Freemius::_get_user_by_email( $current_user->user_email );
97
+
98
+ $activate_with_current_user = (
99
+ is_object( $fs_user ) &&
100
+ ! $is_pending_activation &&
101
+ // If requires a license for activation, use the user associated with the license for the opt-in.
102
+ ! $require_license_key &&
103
+ ! $is_network_level_activation
104
+ );
105
+
106
+ $optin_params = $fs->get_opt_in_params( array(), $is_network_level_activation );
107
+ $sites = isset( $optin_params['sites'] ) ? $optin_params['sites'] : array();
108
+
109
+ $is_network_upgrade_mode = ( fs_is_network_admin() && $fs->is_network_upgrade_mode() );
110
+
111
+ /* translators: %s: name (e.g. Hey John,) */
112
+ $hey_x_text = esc_html( sprintf( fs_text_x_inline( 'Hey %s,', 'greeting', 'hey-x', $slug ), $first_name ) );
113
+
114
+ $is_gdpr_required = ( ! $is_pending_activation && ! $require_license_key ) ?
115
+ FS_GDPR_Manager::instance()->is_required() :
116
+ false;
117
+
118
+ if ( is_null( $is_gdpr_required ) ) {
119
+ $is_gdpr_required = $fs->fetch_and_store_current_user_gdpr_anonymously();
120
+ }
121
+ ?>
122
+ <?php
123
+ if ( $is_optin_dialog ) { ?>
124
+ <div id="fs_theme_connect_wrapper">
125
+ <?php
126
+ if ( $show_close_button ) { ?>
127
+ <button class="close dashicons dashicons-no"><span class="screen-reader-text">Close connect dialog</span>
128
+ </button>
129
+ <?php
130
+ }
131
+ ?>
132
+ <?php
133
+ }
134
+
135
+ /**
136
+ * Allows developers to include custom HTML before the opt-in content.
137
+ *
138
+ * @author Vova Feldman
139
+ * @since 2.3.2
140
+ */
141
+ $fs->do_action( 'connect/before' );
142
+ ?>
143
+ <div id="fs_connect"
144
+ class="wrap<?php if ( ! fs_is_network_admin() && ( ! $fs->is_enable_anonymous() || $is_pending_activation || $require_license_key ) ) {
145
+ echo ' fs-anonymous-disabled';
146
+ } ?><?php echo $require_license_key ? ' require-license-key' : '' ?>">
147
+ <div class="fs-visual">
148
+ <b class="fs-site-icon"><i class="dashicons dashicons-wordpress"></i></b>
149
+ <i class="dashicons dashicons-plus fs-first"></i>
150
+ <?php
151
+ $vars = array( 'id' => $fs->get_id() );
152
+ fs_require_once_template( 'plugin-icon.php', $vars );
153
+ ?>
154
+ <i class="dashicons dashicons-plus fs-second"></i>
155
+ <img class="fs-connect-logo" width="80" height="80" src="//img.freemius.com/connect-logo.png"/>
156
+ </div>
157
+ <div class="fs-content">
158
+ <?php if ( ! empty( $error ) ) : ?>
159
+ <p class="fs-error"><?php echo esc_html( $error ) ?></p>
160
+ <?php endif ?>
161
+ <p><?php
162
+ $button_label = fs_text_inline( 'Allow & Continue', 'opt-in-connect', $slug );
163
+ $message = '';
164
+
165
+ if ( $is_pending_activation ) {
166
+ $button_label = fs_text_inline( 'Re-send activation email', 'resend-activation-email', $slug );
167
+
168
+ $message = $fs->apply_filters( 'pending_activation_message', sprintf(
169
+ /* translators: %s: name (e.g. Thanks John!) */
170
+ fs_text_inline( 'Thanks %s!', 'thanks-x', $slug ) . '<br>' .
171
+ fs_text_inline( '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.', 'pending-activation-message', $slug ),
172
+ $first_name,
173
+ '<b>' . $fs->get_plugin_name() . '</b>',
174
+ '<b>' . $current_user->user_email . '</b>',
175
+ fs_text_inline( 'complete the install', 'complete-the-install', $slug )
176
+ ) );
177
+ } else if ( $require_license_key ) {
178
+ $button_label = $is_network_upgrade_mode ?
179
+ fs_text_inline( 'Activate License', 'agree-activate-license', $slug ) :
180
+ fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug );
181
+
182
+ $message = $fs->apply_filters(
183
+ 'connect-message_on-premium',
184
+ sprintf( fs_text_inline( 'Welcome to %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '<b>' . $fs->get_plugin_name() . '</b>' ),
185
+ $first_name,
186
+ $fs->get_plugin_name()
187
+ );
188
+ } else {
189
+ $filter = 'connect_message';
190
+ $default_optin_message = $is_gdpr_required ?
191
+ fs_text_inline( 'Never miss an important update - opt in to our security & feature updates notifications, educational content, offers, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug) :
192
+ fs_text_inline( 'Never miss an important update - opt in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.', 'connect-message', $slug);
193
+
194
+ if ( $fs->is_plugin_update() ) {
195
+ // If Freemius was added on a plugin update, set different
196
+ // opt-in message.
197
+ $default_optin_message = $is_gdpr_required ?
198
+ fs_text_inline( '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.', 'connect-message_on-update', $slug ) :
199
+ fs_text_inline( '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.', 'connect-message_on-update', $slug );
200
+
201
+ // If user customized the opt-in message on update, use
202
+ // that message. Otherwise, fallback to regular opt-in
203
+ // custom message if exist.
204
+ if ( $fs->has_filter( 'connect_message_on_update' ) ) {
205
+ $filter = 'connect_message_on_update';
206
+ }
207
+ }
208
+
209
+ $message = $fs->apply_filters(
210
+ $filter,
211
+ ($is_network_upgrade_mode ?
212
+ '' :
213
+ /* translators: %s: name (e.g. Hey John,) */
214
+ $hey_x_text . '<br>'
215
+ ) .
216
+ sprintf(
217
+ esc_html( $default_optin_message ),
218
+ '<b>' . esc_html( $fs->get_plugin_name() ) . '</b>',
219
+ '<b>' . $current_user->user_login . '</b>',
220
+ '<a href="' . $site_url . '" target="_blank" rel="noopener noreferrer">' . $site_url . '</a>',
221
+ $freemius_link
222
+ ),
223
+ $first_name,
224
+ $fs->get_plugin_name(),
225
+ $current_user->user_login,
226
+ '<a href="' . $site_url . '" target="_blank" rel="noopener noreferrer">' . $site_url . '</a>',
227
+ $freemius_link,
228
+ $is_gdpr_required
229
+ );
230
+ }
231
+
232
+ if ( $is_network_upgrade_mode ) {
233
+ $network_integration_text = esc_html( fs_text_inline( 'We\'re excited to introduce the Freemius network-level integration.', 'connect_message_network_upgrade', $slug ) );
234
+
235
+ if ($is_premium_code){
236
+ $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %d site(s) that are still pending license activation.', 'connect_message_network_upgrade-premium', $slug ), count( $sites ) );
237
+
238
+ $message .= '<br><br>' . sprintf( fs_text_inline( 'If you\'d like to use the %s on those sites, please enter your license key below and click the activation button.', 'connect_message_network_upgrade-premium-activate-license', $slug ), $is_premium_only ? $fs->get_module_label( true ) : sprintf(
239
+ /* translators: %s: module type (plugin, theme, or add-on) */
240
+ fs_text_inline( "%s's paid features", 'x-paid-features', $slug ),
241
+ $fs->get_module_label( true )
242
+ ) );
243
+
244
+ /* translators: %s: module type (plugin, theme, or add-on) */
245
+ $message .= ' ' . sprintf( fs_text_inline( 'Alternatively, you can skip it for now and activate the license later, in your %s\'s network-level Account page.', 'connect_message_network_upgrade-premium-skip-license', $slug ), $fs->get_module_label( true ) );
246
+ }else {
247
+ $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %s site(s) in the network that are still pending your attention.', 'connect_message_network_upgrade-free', $slug ), count( $sites ) ) . '<br><br>' . ( fs_starts_with( $message, $hey_x_text . '<br>' ) ? substr( $message, strlen( $hey_x_text . '<br>' ) ) : $message );
248
+ }
249
+ }
250
+
251
+ echo $message;
252
+ ?></p>
253
+ <?php if ( $require_license_key ) : ?>
254
+ <div class="fs-license-key-container">
255
+ <input id="fs_license_key" name="fs_key" type="text" required maxlength="<?php echo $fs->apply_filters('license_key_maxlength', 32) ?>"
256
+ placeholder="<?php fs_esc_attr_echo_inline( 'License key', 'license-key', $slug ) ?>" tabindex="1"/>
257
+ <i class="dashicons dashicons-admin-network"></i>
258
+ <a class="show-license-resend-modal show-license-resend-modal-<?php echo $fs->get_unique_affix() ?>"
259
+ href="#"><?php fs_esc_html_echo_inline( "Can't find your license key?", 'cant-find-license-key', $slug ); ?></a>
260
+ </div>
261
+
262
+ <?php
263
+ /**
264
+ * Allows developers to include custom HTML after the license input container.
265
+ *
266
+ * @author Vova Feldman
267
+ * @since 2.1.2
268
+ */
269
+ $fs->do_action( 'connect/after_license_input' );
270
+ ?>
271
+
272
+ <?php
273
+ $send_updates_text = sprintf(
274
+ '%s<span class="action-description"> - %s</span>',
275
+ $fs->get_text_inline( 'Yes', 'yes' ),
276
+ $fs->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' )
277
+ );
278
+
279
+ $do_not_send_updates_text = sprintf(
280
+ '%s<span class="action-description"> - %s</span>',
281
+ $fs->get_text_inline( 'No', 'no' ),
282
+ sprintf(
283
+ $fs->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ),
284
+ '<span class="underlined">',
285
+ '</span>'
286
+ )
287
+ );
288
+ ?>
289
+ <div id="fs_marketing_optin">
290
+ <span class="fs-message"><?php fs_echo_inline( "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:", 'contact-for-updates' ) ?></span>
291
+ <div class="fs-input-container">
292
+ <label>
293
+ <input type="radio" name="allow-marketing" value="true" tabindex="1" />
294
+ <span class="fs-input-label"><?php echo $send_updates_text ?></span>
295
+ </label>
296
+ <label>
297
+ <input type="radio" name="allow-marketing" value="false" tabindex="1" />
298
+ <span class="fs-input-label"><?php echo $do_not_send_updates_text ?></span>
299
+ </label>
300
+ </div>
301
+ </div>
302
+ <?php endif ?>
303
+ <?php if ( $is_network_level_activation ) : ?>
304
+ <?php
305
+ $vars = array(
306
+ 'id' => $fs->get_id(),
307
+ 'sites' => $sites,
308
+ 'require_license_key' => $require_license_key
309
+ );
310
+
311
+ echo fs_get_template( 'partials/network-activation.php', $vars );
312
+ ?>
313
+ <?php endif ?>
314
+ </div>
315
+ <div class="fs-actions">
316
+ <?php if ( $fs->is_enable_anonymous() && ! $is_pending_activation && ( ! $require_license_key || $is_network_upgrade_mode ) ) : ?>
317
+ <a id="skip_activation" href="<?php echo fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_skip_activation' ), $is_network_level_activation ), $fs->get_unique_affix() . '_skip_activation' ) ?>"
318
+ class="button button-secondary" tabindex="2"><?php fs_esc_html_echo_x_inline( 'Skip', 'verb', 'skip', $slug ) ?></a>
319
+ <?php endif ?>
320
+ <?php if ( $is_network_level_activation && $fs->apply_filters( 'show_delegation_option', true ) ) : ?>
321
+ <a id="delegate_to_site_admins" class="fs-tooltip-trigger <?php echo is_rtl() ? ' rtl' : '' ?>" href="<?php echo fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_delegate_activation' ) ), $fs->get_unique_affix() . '_delegate_activation' ) ?>"><?php fs_esc_html_echo_inline( 'Delegate to Site Admins', 'delegate-to-site-admins', $slug ) ?><span class="fs-tooltip"><?php fs_esc_html_echo_inline( 'If you click it, this decision will be delegated to the sites administrators.', 'delegate-sites-tooltip', $slug ) ?></span></a>
322
+ <?php endif ?>
323
+ <?php if ( $activate_with_current_user ) : ?>
324
+ <form action="" method="POST">
325
+ <input type="hidden" name="fs_action"
326
+ value="<?php echo $fs->get_unique_affix() ?>_activate_existing">
327
+ <?php wp_nonce_field( 'activate_existing_' . $fs->get_public_key() ) ?>
328
+ <input type="hidden" name="is_extensions_tracking_allowed" value="1">
329
+ <button class="button button-primary" tabindex="1"
330
+ type="submit"><?php echo esc_html( $button_label ) ?></button>
331
+ </form>
332
+ <?php else : ?>
333
+ <form method="post" action="<?php echo WP_FS__ADDRESS ?>/action/service/user/install/">
334
+ <?php unset( $optin_params['sites']); ?>
335
+ <?php foreach ( $optin_params as $name => $value ) : ?>
336
+ <input type="hidden" name="<?php echo $name ?>" value="<?php echo esc_attr( $value ) ?>">
337
+ <?php endforeach ?>
338
+ <input type="hidden" name="is_extensions_tracking_allowed" value="1">
339
+ <button class="button button-primary" tabindex="1"
340
+ type="submit"<?php if ( $require_license_key ) {
341
+ echo ' disabled="disabled"';
342
+ } ?>><?php echo esc_html( $button_label ) ?></button>
343
+ </form>
344
+ <?php endif ?>
345
+ <?php if ( $require_license_key ) : ?>
346
+ <a id="license_issues_link" href="<?php echo $fs->apply_filters( 'known_license_issues_url', 'https://freemius.com/help/documentation/wordpress-sdk/license-activation-issues/' ) ?>" target="_blank"><?php fs_esc_html_echo_inline( 'License issues?', 'license-issues', $slug ) ?></a>
347
+ <?php endif ?>
348
+ </div><?php
349
+
350
+ // Set core permission list items.
351
+ $permissions = array();
352
+
353
+ /**
354
+ * When activating a license key the information of the admin is not collected, we gather the user info from the license.
355
+ *
356
+ * @since 2.3.2
357
+ * @author Vova Feldman
358
+ */
359
+ if ( ! $require_license_key ) {
360
+ $permissions['profile'] = array(
361
+ 'icon-class' => 'dashicons dashicons-admin-users',
362
+ 'label' => $fs->get_text_inline( 'Your Profile Overview', 'permissions-profile' ),
363
+ 'desc' => $fs->get_text_inline( 'Name and email address', 'permissions-profile_desc' ),
364
+ 'priority' => 5,
365
+ );
366
+ }
367
+
368
+ $permissions['site'] = array(
369
+ 'icon-class' => 'dashicons dashicons-admin-settings',
370
+ 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can manage and control your license remotely from the User Dashboard.', 'permissions-site_tooltip' ), $fs->get_module_type() ) : '' ),
371
+ 'label' => $fs->get_text_inline( 'Your Site Overview', 'permissions-site' ),
372
+ 'desc' => $fs->get_text_inline( 'Site URL, WP version, PHP info', 'permissions-site_desc' ),
373
+ 'priority' => 10,
374
+ );
375
+
376
+ if ( ! $require_license_key ) {
377
+ $permissions['notices'] = array(
378
+ 'icon-class' => 'dashicons dashicons-testimonial',
379
+ 'label' => $fs->get_text_inline( 'Admin Notices', 'permissions-admin-notices' ),
380
+ 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ),
381
+ 'priority' => 13,
382
+ );
383
+ }
384
+
385
+ $permissions['events'] = array(
386
+ 'icon-class' => 'dashicons dashicons-admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ),
387
+ 'tooltip' => ( $require_license_key ? sprintf( $fs->get_text_inline( 'So you can reuse the license when the %s is no longer active.', 'permissions-events_tooltip' ), $fs->get_module_type() ) : '' ),
388
+ 'label' => sprintf( $fs->get_text_inline( 'Current %s Status', 'permissions-events' ), ucfirst( $fs->get_module_type() ) ),
389
+ 'desc' => $fs->get_text_inline( 'Active, deactivated, or uninstalled', 'permissions-events_desc' ),
390
+ 'priority' => 20,
391
+ );
392
+
393
+ // Add newsletter permissions if enabled.
394
+ if ( $is_gdpr_required || $fs->is_permission_requested( 'newsletter' ) ) {
395
+ $permissions['newsletter'] = array(
396
+ 'icon-class' => 'dashicons dashicons-email-alt',
397
+ 'label' => $fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ),
398
+ 'desc' => $fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ),
399
+ 'priority' => 15,
400
+ );
401
+ }
402
+
403
+ $permissions['extensions'] = array(
404
+ 'icon-class' => 'dashicons dashicons-menu',
405
+ 'label' => $fs->get_text_inline( 'Plugins & Themes', 'permissions-extensions' ) . ( $require_license_key ? ' (' . $fs->get_text_inline( 'optional' ) . ')' : '' ),
406
+ 'tooltip' => $fs->get_text_inline( 'To help us troubleshoot any potential issues that may arise from other plugin or theme conflicts.', 'permissions-events_tooltip' ),
407
+ 'desc' => $fs->get_text_inline( 'Title, slug, version, and is active', 'permissions-extensions_desc' ),
408
+ 'priority' => 25,
409
+ 'optional' => true,
410
+ 'default' => $fs->apply_filters( 'permission_extensions_default', ! $require_license_key )
411
+ );
412
+
413
+ // Allow filtering of the permissions list.
414
+ $permissions = $fs->apply_filters( 'permission_list', $permissions );
415
+
416
+ // Sort by priority.
417
+ uasort( $permissions, 'fs_sort_by_priority' );
418
+
419
+ if ( ! empty( $permissions ) ) : ?>
420
+ <div class="fs-permissions">
421
+ <?php if ( $require_license_key ) : ?>
422
+ <p class="fs-license-sync-disclaimer"><?php
423
+ echo sprintf(
424
+ fs_esc_html_inline( 'The %1$s will periodically send %2$s to %3$s for security & feature updates delivery, and license management.', 'license-sync-disclaimer', $slug ),
425
+ $fs->get_module_label( true ),
426
+ sprintf('<a class="fs-trigger" href="#" tabindex="1">%s</a>', fs_esc_html_inline('diagnostic data', 'send-data')),
427
+ '<a class="fs-tooltip-trigger' . (is_rtl() ? ' rtl' : '') . '" href="' . $freemius_site_url . '" target="_blank" rel="noopener" tabindex="1">freemius.com <i class="dashicons dashicons-editor-help" style="text-decoration: none;"><span class="fs-tooltip" style="width: 170px">' . $fs->get_text_inline( 'Freemius is our licensing and software updates engine', 'permissions-extensions_desc' ) . '</span></i></a>'
428
+ ) ?></p>
429
+ <?php else : ?>
430
+ <a class="fs-trigger" href="#" tabindex="1"><?php fs_esc_html_echo_inline( 'What permissions are being granted?', 'what-permissions', $slug ) ?></a>
431
+ <?php endif ?>
432
+ <ul><?php
433
+ foreach ( $permissions as $id => $permission ) : ?>
434
+ <li id="fs-permission-<?php echo esc_attr( $id ); ?>"
435
+ class="fs-permission fs-<?php echo esc_attr( $id ); ?>">
436
+ <i class="<?php echo esc_attr( $permission['icon-class'] ); ?>"></i>
437
+ <?php if ( isset( $permission['optional'] ) && true === $permission['optional'] ) : ?>
438
+ <div class="fs-switch fs-small fs-round fs-<?php echo (! isset( $permission['default'] ) || true === $permission['default'] ) ? 'on' : 'off' ?>">
439
+ <div class="fs-toggle"></div>
440
+ </div>
441
+ <?php endif ?>
442
+
443
+ <div class="fs-permission-description">
444
+ <span<?php if ( ! empty($permission['tooltip']) ) : ?> class="fs-tooltip-trigger"<?php endif ?>><?php echo esc_html( $permission['label'] ); ?><?php if ( ! empty($permission['tooltip']) ) : ?><i class="dashicons dashicons-editor-help"><span class="fs-tooltip" style="width: 200px"><?php echo $permission['tooltip'] ?></span></i><?php endif ?></span>
445
+
446
+ <p><?php echo esc_html( $permission['desc'] ); ?></p>
447
+ </div>
448
+ </li>
449
+ <?php endforeach; ?>
450
+ </ul>
451
+ </div>
452
+ <?php endif ?>
453
+ <?php if ( $is_premium_code && $is_freemium ) : ?>
454
+ <div class="fs-freemium-licensing">
455
+ <p>
456
+ <?php if ( $require_license_key ) : ?>
457
+ <?php fs_esc_html_echo_inline( 'Don\'t have a license key?', 'dont-have-license-key', $slug ) ?>
458
+ <a data-require-license="false" tabindex="1"><?php fs_esc_html_echo_inline( 'Activate Free Version', 'activate-free-version', $slug ) ?></a>
459
+ <?php else : ?>
460
+ <?php fs_echo_inline( 'Have a license key?', 'have-license-key', $slug ) ?>
461
+ <a data-require-license="true" tabindex="1"><?php fs_esc_html_echo_inline( 'Activate License', 'activate-license', $slug ) ?></a>
462
+ <?php endif ?>
463
+ </p>
464
+ </div>
465
+ <?php endif ?>
466
+ <div class="fs-terms">
467
+ <a href="https://freemius.com/privacy/" target="_blank" rel="noopener"
468
+ tabindex="1"><?php fs_esc_html_echo_inline( 'Privacy Policy', 'privacy-policy', $slug ) ?></a>
469
+ &nbsp;&nbsp;-&nbsp;&nbsp;
470
+ <a href="<?php echo $require_license_key ? $freemius_plugin_terms_url : $freemius_usage_tracking_url ?>" target="_blank" rel="noopener" tabindex="1"><?php $require_license_key ? fs_echo_inline( 'License Agreement', 'license-agreement', $slug ) : fs_echo_inline( 'Terms of Service', 'tos', $slug ) ?></a>
471
+ </div>
472
+ </div>
473
+ <?php
474
+ /**
475
+ * Allows developers to include custom HTML after the opt-in content.
476
+ *
477
+ * @author Vova Feldman
478
+ * @since 2.3.2
479
+ */
480
+ $fs->do_action( 'connect/after' );
481
+
482
+ if ( $is_optin_dialog ) { ?>
483
+ </div>
484
+ <?php
485
+ }
486
+ ?>
487
+ <script type="text/javascript">
488
+ (function ($) {
489
+ var $html = $('html');
490
+
491
+ <?php
492
+ if ( $is_optin_dialog ) {
493
+ if ( $show_close_button ) { ?>
494
+ var $themeConnectWrapper = $('#fs_theme_connect_wrapper');
495
+
496
+ $themeConnectWrapper.find('button.close').on('click', function () {
497
+ <?php if ( ! empty( $previous_theme_activation_url ) ) { ?>
498
+ location.href = '<?php echo html_entity_decode( $previous_theme_activation_url ); ?>';
499
+ <?php } else { ?>
500
+ $themeConnectWrapper.remove();
501
+ $html.css({overflow: $html.attr('fs-optin-overflow')});
502
+ <?php } ?>
503
+ });
504
+ <?php
505
+ }
506
+ ?>
507
+
508
+ $html.attr('fs-optin-overflow', $html.css('overflow'));
509
+ $html.css({overflow: 'hidden'});
510
+
511
+ <?php
512
+ }
513
+ ?>
514
+
515
+ var $primaryCta = $('.fs-actions .button.button-primary'),
516
+ primaryCtaLabel = $primaryCta.html(),
517
+ $form = $('.fs-actions form'),
518
+ isNetworkActive = <?php echo $is_network_level_activation ? 'true' : 'false' ?>,
519
+ requireLicenseKey = <?php echo $require_license_key ? 'true' : 'false' ?>,
520
+ hasContextUser = <?php echo $activate_with_current_user ? 'true' : 'false' ?>,
521
+ isNetworkUpgradeMode = <?php echo $is_network_upgrade_mode ? 'true' : 'false' ?>,
522
+ $licenseSecret,
523
+ $licenseKeyInput = $('#fs_license_key'),
524
+ pauseCtaLabelUpdate = false,
525
+ isNetworkDelegating = false,
526
+ /**
527
+ * @author Leo Fajardo (@leorw)
528
+ * @since 2.1.0
529
+ */
530
+ resetLoadingMode = function() {
531
+ // Reset loading mode.
532
+ $primaryCta.html(primaryCtaLabel);
533
+ $primaryCta.prop('disabled', false);
534
+ $(document.body).css({'cursor': 'auto'});
535
+ $('.fs-loading').removeClass('fs-loading');
536
+
537
+ console.log('resetLoadingMode - Primary button was enabled');
538
+ },
539
+ setLoadingMode = function () {
540
+ $(document.body).css({'cursor': 'wait'});
541
+ };
542
+
543
+ $('.fs-actions .button').on('click', function () {
544
+ setLoadingMode();
545
+
546
+ var $this = $(this);
547
+
548
+ setTimeout(function () {
549
+ if ( ! requireLicenseKey || ! $marketingOptin.hasClass( 'error' ) ) {
550
+ $this.attr('disabled', 'disabled');
551
+ }
552
+ }, 200);
553
+ });
554
+
555
+ if ( isNetworkActive ) {
556
+ var
557
+ $multisiteOptionsContainer = $( '.fs-multisite-options-container' ),
558
+ $allSitesOptions = $( '.fs-all-sites-options' ),
559
+ $applyOnAllSites = $( '.fs-apply-on-all-sites-checkbox' ),
560
+ $sitesListContainer = $( '.fs-sites-list-container' ),
561
+ totalSites = <?php echo count( $sites ) ?>,
562
+ maxSitesListHeight = null,
563
+ $skipActivationButton = $( '#skip_activation' ),
564
+ $delegateToSiteAdminsButton = $( '#delegate_to_site_admins' ),
565
+ hasAnyInstall = <?php echo ! is_null( $fs->find_first_install() ) ? 'true' : 'false' ?>;
566
+
567
+ $applyOnAllSites.click(function() {
568
+ var isChecked = $( this ).is( ':checked' );
569
+
570
+ if ( isChecked ) {
571
+ $multisiteOptionsContainer.find( '.action' ).removeClass( 'selected' );
572
+ updatePrimaryCtaText( 'allow' );
573
+ }
574
+
575
+ $multisiteOptionsContainer.find( '.action-allow' ).addClass( 'selected' );
576
+
577
+ $skipActivationButton.toggle();
578
+
579
+ $delegateToSiteAdminsButton.toggle();
580
+
581
+ $multisiteOptionsContainer.toggleClass( 'fs-apply-on-all-sites', isChecked );
582
+
583
+ $sitesListContainer.toggle( ! isChecked );
584
+ if ( ! isChecked && null === maxSitesListHeight ) {
585
+ /**
586
+ * Set the visible number of rows to 5 (5 * height of the first row).
587
+ *
588
+ * @author Leo Fajardo (@leorw)
589
+ */
590
+ maxSitesListHeight = ( 5 * $sitesListContainer.find( 'tr:first' ).height() );
591
+ $sitesListContainer.css( 'max-height', maxSitesListHeight );
592
+ }
593
+ });
594
+
595
+ $allSitesOptions.find( '.action' ).click(function( evt ) {
596
+ var actionType = $( evt.target ).data( 'action-type' );
597
+
598
+ $multisiteOptionsContainer.find( '.action' ).removeClass( 'selected' );
599
+ $multisiteOptionsContainer.find( '.action-' + actionType ).toggleClass( 'selected' );
600
+
601
+ updatePrimaryCtaText( actionType );
602
+ });
603
+
604
+ $sitesListContainer.delegate( '.action', 'click', function( evt ) {
605
+ var $this = $( evt.target );
606
+ if ( $this.hasClass( 'selected' ) ) {
607
+ return false;
608
+ }
609
+
610
+ $this.parents( 'tr:first' ).find( '.action' ).removeClass( 'selected' );
611
+ $this.toggleClass( 'selected' );
612
+
613
+ var
614
+ singleSiteActionType = $this.data( 'action-type' ),
615
+ totalSelected = $sitesListContainer.find( '.action-' + singleSiteActionType + '.selected' ).length;
616
+
617
+ $allSitesOptions.find( '.action.selected' ).removeClass( 'selected' );
618
+
619
+ if ( totalSelected === totalSites ) {
620
+ $allSitesOptions.find( '.action-' + singleSiteActionType ).addClass( 'selected' );
621
+
622
+ updatePrimaryCtaText( singleSiteActionType );
623
+ } else {
624
+ updatePrimaryCtaText( 'mixed' );
625
+ }
626
+ });
627
+
628
+ if ( isNetworkUpgradeMode || hasAnyInstall ) {
629
+ $skipActivationButton.click(function(){
630
+ $delegateToSiteAdminsButton.hide();
631
+
632
+ $skipActivationButton.html('<?php fs_esc_js_echo_inline( 'Skipping, please wait', 'skipping-wait', $slug ) ?>...');
633
+
634
+ pauseCtaLabelUpdate = true;
635
+
636
+ // Check all sites to be skipped.
637
+ $allSitesOptions.find('.action.action-skip').click();
638
+
639
+ $form.submit();
640
+
641
+ pauseCtaLabelUpdate = false;
642
+
643
+ return false;
644
+ });
645
+
646
+ $delegateToSiteAdminsButton.click(function(){
647
+ $delegateToSiteAdminsButton.html('<?php fs_esc_js_echo_inline( 'Delegating, please wait', 'delegating-wait', $slug ) ?>...');
648
+
649
+ pauseCtaLabelUpdate = true;
650
+
651
+ /**
652
+ * Set to true so that the form submission handler can differentiate delegation from license
653
+ * activation and the proper AJAX action will be used (when delegating, the action should be
654
+ * `network_activate` and not `activate_license`).
655
+ *
656
+ * @author Leo Fajardo (@leorw)
657
+ * @since 2.3.0
658
+ */
659
+ isNetworkDelegating = true;
660
+
661
+ // Check all sites to be skipped.
662
+ $allSitesOptions.find('.action.action-delegate').click();
663
+
664
+ $form.submit();
665
+
666
+ pauseCtaLabelUpdate = false;
667
+
668
+ /**
669
+ * Set to false so that in case the previous AJAX request has failed, the form submission handler
670
+ * can differentiate license activation from delegation and the proper AJAX action will be used
671
+ * (when activating a license, the action should be `activate_license` and not `network_activate`).
672
+ *
673
+ * @author Leo Fajardo (@leorw)
674
+ * @since 2.3.0
675
+ */
676
+ isNetworkDelegating = false;
677
+
678
+ return false;
679
+ });
680
+ }
681
+ }
682
+
683
+ /**
684
+ * @author Leo Fajardo (@leorw)
685
+ */
686
+ function updatePrimaryCtaText( actionType ) {
687
+ if (pauseCtaLabelUpdate)
688
+ return;
689
+
690
+ var text = '<?php fs_esc_js_echo_inline( 'Continue', 'continue', $slug ) ?>';
691
+
692
+ switch ( actionType ) {
693
+ case 'allow':
694
+ text = '<?php fs_esc_js_echo_inline( 'Allow & Continue', 'opt-in-connect', $slug ) ?>';
695
+ break;
696
+ case 'delegate':
697
+ text = '<?php fs_esc_js_echo_inline( 'Delegate to Site Admins & Continue', 'delegate-to-site-admins-and-continue', $slug ) ?>';
698
+ break;
699
+ case 'skip':
700
+ text = '<?php fs_esc_js_echo_x_inline( 'Skip', 'verb', 'skip', $slug ) ?>';
701
+ break;
702
+ }
703
+
704
+ $primaryCta.html( text );
705
+ }
706
+
707
+ var ajaxOptin = ( requireLicenseKey || isNetworkActive );
708
+
709
+ $form.on('submit', function () {
710
+ var $extensionsPermission = $('#fs-permission-extensions .fs-switch'),
711
+ isExtensionsTrackingAllowed = ($extensionsPermission.length > 0) ?
712
+ $extensionsPermission.hasClass('fs-on') :
713
+ null;
714
+
715
+ if (null === isExtensionsTrackingAllowed) {
716
+ $('input[name=is_extensions_tracking_allowed]').remove();
717
+ } else {
718
+ $('input[name=is_extensions_tracking_allowed]').val(isExtensionsTrackingAllowed ? 1 : 0);
719
+ }
720
+
721
+ /**
722
+ * @author Vova Feldman (@svovaf)
723
+ * @since 1.1.9
724
+ */
725
+ if ( ajaxOptin ) {
726
+ if (!hasContextUser || isNetworkUpgradeMode) {
727
+ var action = null,
728
+ security = null;
729
+
730
+ if ( requireLicenseKey && ! isNetworkDelegating ) {
731
+ action = '<?php echo $fs->get_ajax_action( 'activate_license' ) ?>';
732
+ security = '<?php echo $fs->get_ajax_security( 'activate_license' ) ?>';
733
+ } else {
734
+ action = '<?php echo $fs->get_ajax_action( 'network_activate' ) ?>';
735
+ security = '<?php echo $fs->get_ajax_security( 'network_activate' ) ?>';
736
+ }
737
+
738
+ $('.fs-error').remove();
739
+
740
+ var
741
+ licenseKey = $licenseKeyInput.val(),
742
+ data = {
743
+ action : action,
744
+ security : security,
745
+ license_key: licenseKey,
746
+ module_id : '<?php echo $fs->get_id() ?>'
747
+ };
748
+
749
+ if (
750
+ requireLicenseKey &&
751
+ ! isNetworkDelegating &&
752
+ isMarketingAllowedByLicense.hasOwnProperty(licenseKey)
753
+ ) {
754
+ var
755
+ isMarketingAllowed = null,
756
+ $isMarketingAllowed = $marketingOptin.find( 'input[type="radio"][name="allow-marketing"]:checked');
757
+
758
+
759
+ if ($isMarketingAllowed.length > 0)
760
+ isMarketingAllowed = ('true' == $isMarketingAllowed.val());
761
+
762
+ if ( null == isMarketingAllowedByLicense[ licenseKey ] &&
763
+ null == isMarketingAllowed
764
+ ) {
765
+ $marketingOptin.addClass( 'error' ).show();
766
+ resetLoadingMode();
767
+ return false;
768
+ } else if ( null == isMarketingAllowed ) {
769
+ isMarketingAllowed = isMarketingAllowedByLicense[ licenseKey ];
770
+ }
771
+
772
+ data.is_marketing_allowed = isMarketingAllowed;
773
+
774
+ data.is_extensions_tracking_allowed = isExtensionsTrackingAllowed;
775
+ }
776
+
777
+ $marketingOptin.removeClass( 'error' );
778
+
779
+ if ( isNetworkActive ) {
780
+ var
781
+ sites = [],
782
+ applyOnAllSites = $applyOnAllSites.is( ':checked' );
783
+
784
+ $sitesListContainer.find( 'tr' ).each(function() {
785
+ var
786
+ $this = $( this ),
787
+ includeSite = ( ! requireLicenseKey || applyOnAllSites || $this.find( 'input' ).is( ':checked' ) );
788
+
789
+ if ( ! includeSite )
790
+ return;
791
+
792
+ var site = {
793
+ uid : $this.find( '.uid' ).val(),
794
+ url : $this.find( '.url' ).val(),
795
+ title : $this.find( '.title' ).val(),
796
+ language: $this.find( '.language' ).val(),
797
+ charset : $this.find( '.charset' ).val(),
798
+ blog_id : $this.find( '.blog-id' ).find( 'span' ).text()
799
+ };
800
+
801
+ if ( ! requireLicenseKey) {
802
+ site.action = $this.find('.action.selected').data('action-type');
803
+ } else if ( isNetworkDelegating ) {
804
+ site.action = 'delegate';
805
+ }
806
+
807
+ sites.push( site );
808
+ });
809
+
810
+ data.sites = sites;
811
+
812
+ if ( hasAnyInstall ) {
813
+ data.has_any_install = hasAnyInstall;
814
+ }
815
+ }
816
+
817
+ /**
818
+ * Use the AJAX opt-in when license key is required to potentially
819
+ * process the after install failure hook.
820
+ *
821
+ * @author Vova Feldman (@svovaf)
822
+ * @since 1.2.1.5
823
+ */
824
+ $.ajax({
825
+ url : ajaxurl,
826
+ method : 'POST',
827
+ data : data,
828
+ success: function (result) {
829
+ var resultObj = $.parseJSON(result);
830
+ if (resultObj.success) {
831
+ // Redirect to the "Account" page and sync the license.
832
+ window.location.href = resultObj.next_page;
833
+ } else {
834
+ resetLoadingMode();
835
+
836
+ // Show error.
837
+ $('.fs-content').prepend('<p class="fs-error">' + (resultObj.error.message ? resultObj.error.message : resultObj.error) + '</p>');
838
+ }
839
+ },
840
+ error: function () {
841
+ resetLoadingMode();
842
+ }
843
+ });
844
+
845
+ return false;
846
+ }
847
+ else {
848
+ if (null == $licenseSecret) {
849
+ $licenseSecret = $('<input type="hidden" name="license_secret_key" value="" />');
850
+ $form.append($licenseSecret);
851
+ }
852
+
853
+ // Update secret key if premium only plugin.
854
+ $licenseSecret.val($licenseKeyInput.val());
855
+ }
856
+ }
857
+
858
+ return true;
859
+ });
860
+
861
+ $primaryCta.on('click', function () {
862
+ console.log('Primary button was clicked');
863
+
864
+ $(this).addClass('fs-loading');
865
+ $(this).html('<?php echo esc_js( $is_pending_activation ?
866
+ fs_text_x_inline( 'Sending email', 'as in the process of sending an email', 'sending-email', $slug ) :
867
+ fs_text_x_inline( 'Activating', 'as activating plugin', 'activating', $slug )
868
+ ) ?>...');
869
+ });
870
+
871
+ $('.fs-permissions .fs-trigger').on('click', function () {
872
+ $('.fs-permissions').toggleClass('fs-open');
873
+
874
+ return false;
875
+ });
876
+
877
+ $( '.fs-switch' ).click( function () {
878
+ $(this)
879
+ .toggleClass( 'fs-on' )
880
+ .toggleClass( 'fs-off' );
881
+ });
882
+
883
+ if (requireLicenseKey) {
884
+ /**
885
+ * Submit license key on enter.
886
+ *
887
+ * @author Vova Feldman (@svovaf)
888
+ * @since 1.1.9
889
+ */
890
+ $licenseKeyInput.keypress(function (e) {
891
+ if (e.which == 13) {
892
+ if ('' !== $(this).val()) {
893
+ $primaryCta.click();
894
+ return false;
895
+ }
896
+ }
897
+ });
898
+
899
+ /**
900
+ * Disable activation button when empty license key.
901
+ *
902
+ * @author Vova Feldman (@svovaf)
903
+ * @since 1.1.9
904
+ */
905
+ $licenseKeyInput.on('keyup paste delete cut', function () {
906
+ setTimeout(function () {
907
+ var key = $licenseKeyInput.val();
908
+
909
+ if (key == previousLicenseKey){
910
+ return;
911
+ }
912
+
913
+ if ('' === key) {
914
+ $primaryCta.attr('disabled', 'disabled');
915
+ $marketingOptin.hide();
916
+ } else {
917
+ $primaryCta.prop('disabled', false);
918
+
919
+ if (32 <= key.length){
920
+ fetchIsMarketingAllowedFlagAndToggleOptin();
921
+ } else {
922
+ $marketingOptin.hide();
923
+ }
924
+ }
925
+
926
+ previousLicenseKey = key;
927
+ }, 100);
928
+ }).focus();
929
+ }
930
+
931
+ /**
932
+ * Set license mode trigger URL.
933
+ *
934
+ * @author Vova Feldman (@svovaf)
935
+ * @since 1.1.9
936
+ */
937
+ var
938
+ $connectLicenseModeTrigger = $('#fs_connect .fs-freemium-licensing a'),
939
+ href = window.location.href;
940
+
941
+ if (href.indexOf('?') > 0) {
942
+ href += '&';
943
+ } else {
944
+ href += '?';
945
+ }
946
+
947
+ if ($connectLicenseModeTrigger.length > 0) {
948
+ $connectLicenseModeTrigger.attr(
949
+ 'href',
950
+ href + 'require_license=' + $connectLicenseModeTrigger.attr('data-require-license')
951
+ );
952
+ }
953
+
954
+ //--------------------------------------------------------------------------------
955
+ //region GDPR
956
+ //--------------------------------------------------------------------------------
957
+ var isMarketingAllowedByLicense = {},
958
+ $marketingOptin = $('#fs_marketing_optin'),
959
+ previousLicenseKey = null;
960
+
961
+ if (requireLicenseKey) {
962
+
963
+ var
964
+ afterMarketingFlagLoaded = function () {
965
+ var licenseKey = $licenseKeyInput.val();
966
+
967
+ if (null == isMarketingAllowedByLicense[licenseKey]) {
968
+ $marketingOptin.show();
969
+
970
+ if ($marketingOptin.find('input[type=radio]:checked').length > 0){
971
+ // Focus on button if GDPR opt-in already selected is already selected.
972
+ $primaryCta.focus();
973
+ } else {
974
+ // Focus on the GDPR opt-in radio button.
975
+ $($marketingOptin.find('input[type=radio]')[0]).focus();
976
+ }
977
+ } else {
978
+ $marketingOptin.hide();
979
+ $primaryCta.focus();
980
+ }
981
+ },
982
+ /**
983
+ * @author Leo Fajardo (@leorw)
984
+ * @since 2.1.0
985
+ */
986
+ fetchIsMarketingAllowedFlagAndToggleOptin = function () {
987
+ var licenseKey = $licenseKeyInput.val();
988
+
989
+ if (licenseKey.length < 32) {
990
+ $marketingOptin.hide();
991
+ return;
992
+ }
993
+
994
+ if (isMarketingAllowedByLicense.hasOwnProperty(licenseKey)) {
995
+ afterMarketingFlagLoaded();
996
+ return;
997
+ }
998
+
999
+ $marketingOptin.hide();
1000
+
1001
+ setLoadingMode();
1002
+
1003
+ $primaryCta.addClass('fs-loading');
1004
+ $primaryCta.attr('disabled', 'disabled');
1005
+ $primaryCta.html('<?php fs_esc_js_echo_inline( 'Please wait', 'please-wait', $slug ) ?>...');
1006
+
1007
+ $.ajax({
1008
+ url : ajaxurl,
1009
+ method : 'POST',
1010
+ data : {
1011
+ action : '<?php echo $fs->get_ajax_action( 'fetch_is_marketing_required_flag_value' ) ?>',
1012
+ security : '<?php echo $fs->get_ajax_security( 'fetch_is_marketing_required_flag_value' ) ?>',
1013
+ license_key: licenseKey,
1014
+ module_id : '<?php echo $fs->get_id() ?>'
1015
+ },
1016
+ success: function (result) {
1017
+ resetLoadingMode();
1018
+
1019
+ if (result.success) {
1020
+ result = result.data;
1021
+
1022
+ // Cache result.
1023
+ isMarketingAllowedByLicense[licenseKey] = result.is_marketing_allowed;
1024
+ }
1025
+
1026
+ afterMarketingFlagLoaded();
1027
+ }
1028
+ });
1029
+ };
1030
+
1031
+ $marketingOptin.find( 'input' ).click(function() {
1032
+ $marketingOptin.removeClass( 'error' );
1033
+ });
1034
+ }
1035
+
1036
+ //endregion
1037
+ })(jQuery);
1038
  </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/license-activation.php CHANGED
@@ -1,870 +1,870 @@
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.9
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
-
22
- $cant_find_license_key_text = fs_text_inline( "Can't find your license key?", 'cant-find-license-key', $slug );
23
- $message_above_input_field = fs_text_inline( 'Please enter the license key that you received in the email right after the purchase:', 'activate-license-message', $slug );
24
- $message_below_input_field = '';
25
-
26
- $header_title = $fs->is_free_plan() ?
27
- fs_text_inline( 'Activate License', 'activate-license', $slug ) :
28
- fs_text_inline( 'Update License', 'update-license', $slug );
29
-
30
- if ( $fs->is_registered() ) {
31
- $activate_button_text = $header_title;
32
- } else {
33
- $freemius_site_url = $fs->has_paid_plan() ?
34
- 'https://freemius.com/' :
35
- // Insights platform information.
36
- $fs->get_usage_tracking_terms_url();
37
-
38
- $freemius_link = '<a href="' . $freemius_site_url . '" target="_blank" rel="noopener" tabindex="0">freemius.com</a>';
39
-
40
- $message_below_input_field = sprintf(
41
- fs_text_inline( '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.', 'license-sync-disclaimer', $slug ),
42
- $fs->get_module_label( true ),
43
- $freemius_link
44
- );
45
-
46
- $activate_button_text = fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug );
47
- }
48
-
49
- $license_key_text = fs_text_inline( 'License key', 'license-key' , $slug );
50
-
51
- $is_network_activation = (
52
- $fs->is_network_active() &&
53
- fs_is_network_admin() &&
54
- ! $fs->is_delegated_connection()
55
- );
56
- $network_activation_html = '';
57
-
58
- $sites_details = array();
59
- if ( $is_network_activation ) {
60
- $all_sites = Freemius::get_sites();
61
-
62
- foreach ( $all_sites as $site ) {
63
- $site_details = $fs->get_site_info( $site );
64
-
65
- $blog_id = Freemius::get_site_blog_id( $site );
66
- $install = $fs->get_install_by_blog_id($blog_id);
67
-
68
- if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) {
69
- $site_details['license_id'] = $install->license_id;
70
- }
71
-
72
- $sites_details[] = $site_details;
73
- }
74
-
75
- if ( $is_network_activation ) {
76
- $vars = array(
77
- 'id' => $fs->get_id(),
78
- 'sites' => $sites_details,
79
- 'require_license_key' => true
80
- );
81
-
82
- $network_activation_html = fs_get_template( 'partials/network-activation.php', $vars );
83
- }
84
- }
85
-
86
- $premium_licenses = $fs->get_available_premium_licenses();
87
- $available_licenses = array();
88
- foreach ( $premium_licenses as $premium_license ) {
89
- $activations_left = $premium_license->left();
90
- if ( ! ( $activations_left > 0 ) ) {
91
- continue;
92
- }
93
-
94
- $available_licenses[ $activations_left . '_' . $premium_license->id ] = $premium_license;
95
- }
96
-
97
- $total_available_licenses = count( $available_licenses );
98
- if ( $total_available_licenses > 0 ) {
99
- $license_input_html = <<< HTML
100
- <div class="fs-license-options-container">
101
- <table>
102
- <tbody>
103
- <tr class="fs-available-license-key-container">
104
- <td><input type="radio" name="license_type" value="available"></td>
105
- <td>
106
- HTML;
107
-
108
- if ( $total_available_licenses > 1 ) {
109
- // Sort the licenses by number of activations left in descending order.
110
- krsort( $available_licenses );
111
-
112
- $license_input_html .= '<select class="fs-licenses">';
113
-
114
- /**
115
- * @var FS_Plugin_License $license
116
- */
117
- foreach ( $available_licenses as $license ) {
118
- $plan = $fs->_get_plan_by_id( $license->plan_id );
119
-
120
- $label = sprintf(
121
- "%s-Site %s License - %s",
122
- ( 1 == $license->quota ?
123
- 'Single' :
124
- ( $license->is_unlimited() ? 'Unlimited' : $license->quota )
125
- ),
126
- ( is_object( $plan ) ? $plan->title : '' ),
127
- $license->get_html_escaped_masked_secret_key()
128
- );
129
-
130
- $license_input_html .= "<option data-id='{$license->id}' value='{$license->secret_key}' data-left='{$license->left()}'>{$label}</option>";
131
- }
132
-
133
- $license_input_html .= '</select>';
134
- } else {
135
- $available_licenses = array_values( $available_licenses );
136
-
137
- /**
138
- * @var FS_Plugin_License $available_license
139
- */
140
- $available_license = $available_licenses[0];
141
- $value = sprintf(
142
- "%s-Site %s License - %s",
143
- ( 1 == $available_license->quota ?
144
- 'Single' :
145
- ( $available_license->is_unlimited() ? 'Unlimited' : $available_license->quota )
146
- ),
147
- $fs->_get_plan_by_id( $available_license->plan_id )->title,
148
- $available_license->get_html_escaped_masked_secret_key()
149
- );
150
-
151
- $license_input_html .= <<< HTML
152
- <input
153
- class="fs-available-license-key"
154
- type="text"
155
- value="{$value}"
156
- data-id="{$available_license->id}"
157
- data-license-key="{$available_license->secret_key}"
158
- data-left="{$available_license->left()}"
159
- readonly />
160
- HTML;
161
- }
162
-
163
- $license_input_html .= <<< HTML
164
- </td>
165
- </tr>
166
- <tr>
167
- <td><input type="radio" name="license_type" value="other"></td>
168
- <td class="fs-other-license-key-container">
169
- <label for="other_license_key_{$unique_affix}">Other: </label>
170
- <div>
171
- <input id="other_license_key_{$unique_affix}" class="fs-license-key" type="text" placeholder="Enter license key" tabindex="1">
172
- </div>
173
- </td>
174
- </tr>
175
- </tbody>
176
- </table>
177
- </div>
178
- HTML;
179
- } else {
180
- $license_input_html = "<input class='fs-license-key' type='text' placeholder='{$license_key_text}' tabindex='1' />";
181
- }
182
-
183
- $ownership_change_option_text = fs_text_inline( "Associate with the license owner's account.", 'associate-account-with-license-owner', $slug );
184
- $ownership_change_option_html = "<div class='ownership-change-option-container' style='display: none'><label><input type='checkbox' /> <strong>{$ownership_change_option_text}</strong></label></div>";
185
-
186
- /**
187
- * IMPORTANT:
188
- * DO NOT ADD MAXLENGTH OR LIMIT THE LICENSE KEY LENGTH SINCE
189
- * WE DO WANT TO ALLOW INPUT OF LONGER KEYS (E.G. WooCommerce Keys)
190
- * FOR MIGRATED MODULES.
191
- */
192
- $modal_content_html = <<< HTML
193
- <div class="notice notice-error inline license-activation-message"><p></p></div>
194
- <p>{$message_above_input_field}</p>
195
- {$license_input_html}
196
- <a class="show-license-resend-modal show-license-resend-modal-{$fs->get_unique_affix()}" href="!#" tabindex="2">{$cant_find_license_key_text}</a>
197
- {$network_activation_html}
198
- <p>{$message_below_input_field}</p>
199
- {$ownership_change_option_html}
200
- HTML;
201
-
202
- /**
203
- * Handle the ownership change option if not an add-on or if no license yet is activated for the
204
- * parent product in case of an add-on.
205
- *
206
- * @author Leo Fajardo (@leorw)
207
- * @since 2.3.2
208
- */
209
- $is_user_change_supported = ( ! $fs->is_addon() || ! $fs->get_parent_instance()->has_active_valid_license() );
210
-
211
- fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
212
- ?>
213
- <script type="text/javascript">
214
- (function( $ ) {
215
- $( document ).ready(function() {
216
- var modalContentHtml = <?php echo json_encode($modal_content_html); ?>,
217
- modalHtml =
218
- '<div class="fs-modal fs-modal-license-activation fs-modal-license-activation-<?php echo $unique_affix ?>">'
219
- + ' <div class="fs-modal-dialog">'
220
- + ' <div class="fs-modal-header">'
221
- + ' <h4><?php echo esc_js($header_title) ?></h4>'
222
- + ' <a href="!#" class="fs-close"><i class="dashicons dashicons-no" title="<?php echo esc_js( fs_text_x_inline( 'Dismiss', 'as close a window', 'dismiss', $slug ) ) ?>"></i></a>'
223
- + ' </div>'
224
- + ' <div class="fs-modal-body">'
225
- + ' <div class="fs-modal-panel active">' + modalContentHtml + '</div>'
226
- + ' </div>'
227
- + ' <div class="fs-modal-footer">'
228
- + ' <button class="button button-secondary button-close" tabindex="4"><?php fs_esc_js_echo_inline( 'Cancel', 'cancel', $slug ) ?></button>'
229
- + ' <button class="button button-primary button-activate-license" tabindex="3"><?php echo esc_js( $activate_button_text ) ?></button>'
230
- + ' </div>'
231
- + ' </div>'
232
- + '</div>',
233
- $modal = $(modalHtml),
234
- $activateLicenseButton = $modal.find('.button-activate-license'),
235
- $licenseKeyInput = $modal.find( 'input.fs-license-key' ),
236
- $licenseActivationMessage = $modal.find( '.license-activation-message' ),
237
- isNetworkActivation = <?php echo $is_network_activation ? 'true' : 'false' ?>,
238
- isUserChangeSupported = <?php echo $is_user_change_supported ? 'true' : 'false' ?>,
239
- isSingleSiteActivation = false,
240
- $ownershipChangeOptionContainer = $modal.find( '.ownership-change-option-container' ),
241
- $body = $( 'body' );
242
-
243
- $modal.appendTo( $body );
244
-
245
- var
246
- $licensesDropdown = $modal.find( '.fs-licenses' ),
247
- $licenseTypes = $modal.find( 'input[type="radio"][name="license_type"]' ),
248
- $applyOnAllSites = $modal.find( '.fs-apply-on-all-sites-checkbox' ),
249
- $sitesListContainer = $modal.find( '.fs-sites-list-container' ),
250
- $availableLicenseKey = $modal.find( '.fs-available-license-key' ),
251
- $otherLicenseKey = $modal.find( '#other_license_key_<?php echo $unique_affix ?>' ),
252
- $multisiteOptionsContainer = $modal.find( '.fs-multisite-options-container' ),
253
- $activationsLeft = null,
254
- hasLicensesDropdown = ( $licensesDropdown.length > 0 ),
255
- hasLicenseTypes = ( $licenseTypes.length > 0 ),
256
- maxSitesListHeight = null,
257
- totalSites = <?php echo count( $sites_details ) ?>,
258
- singleBlogID = null;
259
-
260
- var
261
- previousLicenseKey = null,
262
- otherLicenseOwnerID = null,
263
- /**
264
- * @author Leo Fajardo (@leorw)
265
- * @since 2.3.2
266
- */
267
- resetLoadingMode = function () {
268
- // Reset loading mode.
269
- $activateLicenseButton.text( <?php echo json_encode( $activate_button_text ) ?> );
270
- $activateLicenseButton.prop( 'disabled', false );
271
- $( document.body ).css( { 'cursor': 'auto' } );
272
- $( '.fs-loading' ).removeClass( 'fs-loading' );
273
-
274
- console.log( 'resetLoadingMode - Primary button was enabled' );
275
- },
276
- /**
277
- * @author Leo Fajardo (@leorw)
278
- * @since 2.3.2
279
- */
280
- setLoadingMode = function () {
281
- $( document.body ).css( { 'cursor': 'wait' } );
282
- },
283
- /**
284
- * @author Leo Fajardo (@leorw)
285
- * @since 2.3.2
286
- */
287
- afterLicenseUserDataLoaded = function () {
288
- if (
289
- null !== otherLicenseOwnerID &&
290
- otherLicenseOwnerID != <?php echo $fs->is_registered() ? $fs->get_user()->id : 'null' ?>
291
- ) {
292
- $ownershipChangeOptionContainer.show();
293
- } else {
294
- $ownershipChangeOptionContainer.hide();
295
- $activateLicenseButton.focus();
296
- }
297
- },
298
- /**
299
- * @author Leo Fajardo (@leorw)
300
- * @since 2.3.2
301
- */
302
- fetchLicenseUserData = function () {
303
- var hideAndUncheckUserChangeCheckbox = ( ! isUserChangeSupported ),
304
- otherLicenseKeyIsSelected = isOtherLicenseKeySelected();
305
-
306
- if ( ! hideAndUncheckUserChangeCheckbox ) {
307
- // User change is supported only on the site level.
308
- hideAndUncheckUserChangeCheckbox = ( isNetworkActivation || isSingleSiteActivation );
309
- }
310
-
311
- if ( ! hideAndUncheckUserChangeCheckbox ) {
312
- hideAndUncheckUserChangeCheckbox = ( hasLicenseTypes && ! otherLicenseKeyIsSelected );
313
- }
314
-
315
- var licenseKey = $licenseKeyInput.val().trim();
316
-
317
- if ( ! hideAndUncheckUserChangeCheckbox && otherLicenseKeyIsSelected ) {
318
- hideAndUncheckUserChangeCheckbox = ( licenseKey.length < 32 );
319
- }
320
-
321
- if ( licenseKey !== previousLicenseKey ) {
322
- // If the license key has not been changed, keep the owner ID in order to prevent another API call.
323
- otherLicenseOwnerID = null;
324
- }
325
-
326
- if ( hideAndUncheckUserChangeCheckbox ) {
327
- $ownershipChangeOptionContainer.hide().find( 'input' ).attr( 'checked', false );
328
-
329
- return;
330
- }
331
-
332
- if ( null !== otherLicenseOwnerID ) {
333
- afterLicenseUserDataLoaded();
334
- return;
335
- }
336
-
337
- setLoadingMode();
338
-
339
- $activateLicenseButton.addClass( 'fs-loading' );
340
- $activateLicenseButton.attr( 'disabled', 'disabled' );
341
- $activateLicenseButton.html( '<?php fs_esc_js_echo_inline( 'Please wait', 'please-wait', $slug ) ?>...' );
342
-
343
- $.ajax( {
344
- url : ajaxurl,
345
- method : 'POST',
346
- data : {
347
- action : '<?php echo $fs->get_ajax_action( 'fetch_is_marketing_required_flag_value' ) ?>',
348
- security : '<?php echo $fs->get_ajax_security( 'fetch_is_marketing_required_flag_value' ) ?>',
349
- license_key: licenseKey,
350
- module_id : '<?php echo $fs->get_id() ?>'
351
- },
352
- success: function ( result ) {
353
- resetLoadingMode();
354
-
355
- if ( result.success ) {
356
- result = result.data;
357
-
358
- // Cache license owner's ID.
359
- otherLicenseOwnerID = result.license_owner_id;
360
- }
361
-
362
- afterLicenseUserDataLoaded();
363
- }
364
- } );
365
- };
366
-
367
- function registerEventHandlers() {
368
- var
369
- $otherLicenseKeyContainer = $modal.find( '.fs-other-license-key-container' );
370
-
371
- if ( isNetworkActivation ) {
372
- $applyOnAllSites.click(function() {
373
- var applyOnAllSites = $( this ).is( ':checked' );
374
-
375
- $multisiteOptionsContainer.toggleClass( 'fs-apply-on-all-sites', applyOnAllSites );
376
-
377
- showSites( ! applyOnAllSites );
378
-
379
- if ( hasValidLicenseKey() && ( applyOnAllSites || hasSelectedSite() ) ) {
380
- enableActivateLicenseButton();
381
- } else {
382
- disableActivateLicenseButton();
383
- }
384
- });
385
-
386
- $sitesListContainer.delegate( 'td:not(:first-child)', 'click', function() {
387
- // If a site row is clicked, trigger a click on the checkbox.
388
- $( this ).parent().find( 'td:first-child input' ).click();
389
- });
390
-
391
- $sitesListContainer.delegate( 'input[type="checkbox"]', 'click', function() {
392
- enableDisableSitesSelection();
393
-
394
- if ( hasValidLicenseKey() && hasSelectedSite() ) {
395
- enableActivateLicenseButton();
396
- } else {
397
- disableActivateLicenseButton();
398
- }
399
- });
400
- }
401
-
402
- if ( hasLicensesDropdown ) {
403
- $licensesDropdown.change(function() {
404
- // When a license is selected, select the associated radio button.
405
- $licenseTypes.filter( '[value="available"]' ).attr( 'checked', true );
406
-
407
- if ( ! isNetworkActivation || $modal.hasClass( 'is-single-site-activation' ) ) {
408
- enableActivateLicenseButton();
409
- return true;
410
- }
411
-
412
- toggleActivationOnAllSites();
413
- })
414
- }
415
-
416
- if ( hasLicenseTypes ) {
417
- $licenseTypes.change(function() {
418
- var
419
- licenseKey = $modal.find( 'input.fs-license-key' ).val().trim(),
420
- otherLicenseKeySelected = isOtherLicenseKeySelected();
421
-
422
- if ( ( licenseKey.length > 0 || ( hasLicenseTypes && ! otherLicenseKeySelected ) ) &&
423
- ( $modal.hasClass( 'is-single-site-activation' ) || ! isNetworkActivation || hasSelectedSite() )
424
- ) {
425
- /**
426
- * If the "other" license is not empty or an available license is selected, enable the activate
427
- * button.
428
- *
429
- * @author Leo Fajardo (@leorw)
430
- */
431
- enableActivateLicenseButton();
432
- } else {
433
- disableActivateLicenseButton();
434
- }
435
-
436
- if ( '' !== licenseKey ) {
437
- fetchLicenseUserData();
438
- }
439
-
440
- if ( ! isNetworkActivation ) {
441
- return;
442
- }
443
-
444
- if ( otherLicenseKeySelected ) {
445
- $applyOnAllSites.attr( 'disabled', false );
446
- enableDisableSitesSelection();
447
- resetActivateLicenseCheckboxLabel();
448
- } else if ( ! $modal.hasClass( 'is-single-site-activation' ) ) {
449
- toggleActivationOnAllSites();
450
- }
451
- });
452
-
453
- if ( ! hasLicensesDropdown ) {
454
- $availableLicenseKey.click(function() {
455
- $licenseTypes.filter( '[value="available"]' ).click();
456
- });
457
- }
458
-
459
- $otherLicenseKeyContainer.click(function() {
460
- $licenseTypes.filter( '[value="other"]' ).click();
461
- });
462
- }
463
-
464
- $body.on( 'click', 'span.activate-license.<?php echo $unique_affix ?> a, .activate-license-trigger.<?php echo $unique_affix ?>', function (evt) {
465
- evt.preventDefault();
466
-
467
- showModal( evt );
468
- });
469
-
470
- var licenseTimeout = null;
471
-
472
- /**
473
- * Disable activation button when license key is empty.
474
- *
475
- * @author Leo Fajardo (@leorw)
476
- * @since 2.3.2
477
- */
478
- $modal.on( 'keyup paste delete cut', 'input.fs-license-key', function () {
479
- clearTimeout(licenseTimeout);
480
-
481
- licenseTimeout = setTimeout( function () {
482
- var licenseKey = $licenseKeyInput.val().trim();
483
-
484
- if ( licenseKey == previousLicenseKey ) {
485
- afterLicenseUserDataLoaded();
486
- return;
487
- }
488
-
489
- if ( '' === licenseKey ) {
490
- disableActivateLicenseButton();
491
- $ownershipChangeOptionContainer.hide();
492
- } else {
493
- enableActivateLicenseButton();
494
-
495
- if ( 32 <= licenseKey.length ) {
496
- fetchLicenseUserData();
497
- } else {
498
- $ownershipChangeOptionContainer.hide();
499
- }
500
- }
501
-
502
- previousLicenseKey = licenseKey;
503
- }, 200 );
504
- } ).focus();
505
-
506
- $modal.on('input propertychange', 'input.fs-license-key', function () {
507
-
508
- var licenseKey = $(this).val().trim();
509
-
510
- /**
511
- * If license key is not empty, enable the license activation button.
512
- */
513
- if ( licenseKey.length > 0 && ( $modal.hasClass( 'is-single-site-activation' ) || ! isNetworkActivation || hasSelectedSite() ) ) {
514
- enableActivateLicenseButton();
515
- }
516
- });
517
-
518
- $modal.on( 'blur', 'input.fs-license-key', function( evt ) {
519
- var
520
- licenseKey = $(this).val().trim(),
521
- $focusedElement = $( evt.relatedTarget ),
522
- hasSelectedAvailableLicense = ( hasLicenseTypes && $focusedElement.parents( '.fs-available-license-key-container' ).length > 0 );
523
-
524
- /**
525
- * If license key is empty, disable the license activation button.
526
- */
527
- if ( ( 0 === licenseKey.length && ( ! hasLicenseTypes || ! hasSelectedAvailableLicense ) ) ||
528
- ( isNetworkActivation && ! hasSelectedSite() )
529
- ) {
530
- disableActivateLicenseButton();
531
- }
532
- });
533
-
534
- $modal.on('click', '.button-activate-license', function (evt) {
535
- evt.preventDefault();
536
-
537
- if ($(this).hasClass('disabled')) {
538
- return;
539
- }
540
-
541
- var
542
- licenseKey = '';
543
-
544
- if ( hasLicenseTypes ) {
545
- if ( isOtherLicenseKeySelected() ) {
546
- licenseKey = $otherLicenseKey.val();
547
- } else {
548
- if ( ! hasLicensesDropdown ) {
549
- licenseKey = $availableLicenseKey.data( 'license-key' );
550
- } else {
551
- licenseKey = $licensesDropdown.val();
552
- }
553
- }
554
- } else {
555
- licenseKey = $licenseKeyInput.val().trim();
556
- }
557
-
558
- disableActivateLicenseButton();
559
-
560
- if (0 === licenseKey.length) {
561
- return;
562
- }
563
-
564
- var data = {
565
- action : '<?php echo $fs->get_ajax_action( 'activate_license' ) ?>',
566
- security : '<?php echo $fs->get_ajax_security( 'activate_license' ) ?>',
567
- license_key: licenseKey,
568
- module_id : '<?php echo $fs->get_id() ?>'
569
- };
570
-
571
- if ( isNetworkActivation ) {
572
- var
573
- sites = [];
574
-
575
- if ( null === singleBlogID ) {
576
- var
577
- applyOnAllSites = $applyOnAllSites.is( ':checked' );
578
-
579
- $sitesListContainer.find( 'tr' ).each(function() {
580
- var
581
- $this = $( this ),
582
- includeSite = ( applyOnAllSites || $this.find( 'input' ).is( ':checked' ) );
583
-
584
- if ( ! includeSite )
585
- return;
586
-
587
- var site = {
588
- uid : $this.find( '.uid' ).val(),
589
- url : $this.find( '.url' ).val(),
590
- title : $this.find( '.title' ).val(),
591
- language: $this.find( '.language' ).val(),
592
- charset : $this.find( '.charset' ).val(),
593
- blog_id : $this.find( '.blog-id' ).find( 'span' ).text()
594
- };
595
-
596
- sites.push( site );
597
- });
598
- } else {
599
- data.blog_id = singleBlogID;
600
- }
601
-
602
- data.sites = sites;
603
- }
604
-
605
- if ( $ownershipChangeOptionContainer.find( 'input:checked' ).length > 0 ) {
606
- data.user_id = otherLicenseOwnerID;
607
- }
608
-
609
- $.ajax({
610
- url: ajaxurl,
611
- method: 'POST',
612
- data: data,
613
- beforeSend: function () {
614
- $activateLicenseButton.text( '<?php fs_esc_js_echo_inline( 'Activating license', 'activating-license', $slug ) ?>...' );
615
- },
616
- success: function( result ) {
617
- var resultObj = $.parseJSON( result );
618
- if ( resultObj.success ) {
619
- closeModal();
620
-
621
- // Redirect to the "Account" page and sync the license.
622
- window.location.href = resultObj.next_page;
623
- } else {
624
- showError( resultObj.error.message ? resultObj.error.message : resultObj.error );
625
- resetActivateLicenseButton();
626
- }
627
- }
628
- });
629
- });
630
-
631
- // If the user has clicked outside the window, close the modal.
632
- $modal.on('click', '.fs-close, .button-secondary', function () {
633
- closeModal();
634
- return false;
635
- });
636
- }
637
-
638
- registerEventHandlers();
639
-
640
- $body.trigger('licenseActivationLoaded');
641
-
642
- /**
643
- * @author Leo Fajardo (@leorw)
644
- * @since 2.0.0
645
- */
646
- function enableDisableSitesSelection() {
647
- var
648
- canApplyOnAllSites = $applyOnAllSites.is( ':enabled' ),
649
- disableSitesSelection = null;
650
-
651
- if ( ! canApplyOnAllSites ) {
652
- var
653
- selectedSites = $sitesListContainer.find( 'input[type="checkbox"]:checked' ).length,
654
- activationsLeft = Math.max( 0, $activationsLeft.data( 'left' ) - selectedSites );
655
-
656
- disableSitesSelection = ( 0 === activationsLeft );
657
-
658
- $activationsLeft.text( activationsLeft );
659
- } else {
660
- disableSitesSelection = false;
661
- }
662
-
663
- $sitesListContainer
664
- .find( 'input[type="checkbox"]:not(:checked)' )
665
- .attr( 'disabled', disableSitesSelection );
666
- }
667
-
668
- /**
669
- * @author Leo Fajardo (@leorw)
670
- * @since 2.0.0
671
- *
672
- * @returns {Boolean}
673
- */
674
- function isOtherLicenseKeySelected() {
675
- return ( hasLicenseTypes && 'other' === $licenseTypes.filter( ':checked' ).val() );
676
- }
677
-
678
- /**
679
- * @author Leo Fajardo (@leorw)
680
- * @since 2.0.0
681
- *
682
- * @returns {Boolean}
683
- */
684
- function hasValidLicenseKey() {
685
- var licenseKey = '';
686
- if ( hasLicenseTypes ) {
687
- if ( 'available' === $licenseTypes.filter( ':checked' ).val() ) {
688
- return true;
689
- } else {
690
- licenseKey = $otherLicenseKey.val();
691
- }
692
- } else {
693
- licenseKey = $modal.find( 'input.fs-license-key' ).val();
694
- }
695
-
696
- return ( licenseKey.trim().length > 0 );
697
- }
698
-
699
- /**
700
- * @author Leo Fajardo (@leorw)
701
- * @since 2.0.0
702
- *
703
- * @returns {Boolean}
704
- */
705
- function hasSelectedSite() {
706
- return ( $applyOnAllSites.is( ':checked' ) ||
707
- $sitesListContainer.find( 'input[type="checkbox"]:checked:not(:disabled)' ).length > 0 );
708
- }
709
-
710
- /**
711
- * @author Leo Fajardo (@leorw)
712
- * @since 2.0.0
713
- */
714
- function toggleActivationOnAllSites() {
715
- var activationsLeft,
716
- licenseID;
717
-
718
- if (hasLicensesDropdown) {
719
- var $selectedOption = $licensesDropdown.find( ':selected' );
720
- activationsLeft = $selectedOption.data('left');
721
- licenseID = $selectedOption.data('id');
722
- } else {
723
- activationsLeft = $availableLicenseKey.data('left');
724
- licenseID = $availableLicenseKey.data('id');
725
- }
726
-
727
- // Cleanup previously auto-selected site.
728
- $modal.find( '.fs-sites-list-container input[type=checkbox]:disabled' )
729
- .attr('disabled', false)
730
- .attr('checked', false);
731
-
732
- var $blogsWithActiveLicense = $modal.find( '.fs-sites-list-container tr[data-license-id=' + licenseID + '] input[type=checkbox]' );
733
-
734
- if ($blogsWithActiveLicense.length > 0) {
735
- $blogsWithActiveLicense.attr('checked', true)
736
- .attr('disabled', true);
737
-
738
- activationsLeft += $blogsWithActiveLicense.length;
739
- }
740
-
741
- if ( activationsLeft >= totalSites ) {
742
- $applyOnAllSites.attr( 'disabled', false );
743
- enableDisableSitesSelection();
744
-
745
- resetActivateLicenseCheckboxLabel();
746
-
747
- return;
748
- }
749
-
750
- $applyOnAllSites.attr( 'checked', false );
751
- $applyOnAllSites.attr( 'disabled', true );
752
-
753
- showSites( true );
754
-
755
- var
756
- activateLicenseCheckboxLabel = '<?php fs_esc_js_echo_inline( 'Choose up to %s site(s) to activate the license on.', 'choose-up-to-n-sites-to-activate-the-license-on', $slug ) ?>';
757
-
758
- activateLicenseCheckboxLabel = activateLicenseCheckboxLabel.replace( '%s', '<span data-left="' + activationsLeft + '" class="activations-left">' + activationsLeft + '</span>' );
759
-
760
- // Update the label of the "Activate license on all sites" checkbox.
761
- $applyOnAllSites.parent().find( 'span' ).html( activateLicenseCheckboxLabel );
762
- $activationsLeft = $modal.find( '.activations-left' );
763
-
764
- if ( hasSelectedSite() ) {
765
- enableActivateLicenseButton();
766
- enableDisableSitesSelection();
767
- } else {
768
- disableActivateLicenseButton();
769
- }
770
- }
771
-
772
- /**
773
- * @author Leo Fajardo (@leorw)
774
- * @since 2.0.0
775
- */
776
- function resetActivateLicenseCheckboxLabel() {
777
- var activateLicenseCheckboxLabel = '<?php fs_esc_js_echo_inline( 'Activate license on all sites in the network.', 'activate-license-on-all-sites-in-the-network', $slug ) ?>';
778
- $applyOnAllSites.parent().find( 'span' ).text( activateLicenseCheckboxLabel );
779
- }
780
-
781
- /**
782
- * @author Leo Fajardo (@leorw)
783
- * @since 2.0.0
784
- *
785
- * @param {Boolean} show
786
- */
787
- function showSites( show ) {
788
- $sitesListContainer.toggle( show );
789
- if ( show && null === maxSitesListHeight ) {
790
- /**
791
- * Set the visible number of rows to 5 (5 * height of the first row).
792
- *
793
- * @author Leo Fajardo (@leorw)
794
- */
795
- maxSitesListHeight = ( 5 * $sitesListContainer.find( 'tr:first' ).height() );
796
- $sitesListContainer.css( 'max-height', maxSitesListHeight );
797
- }
798
- }
799
-
800
- function showModal( evt ) {
801
- resetModal();
802
-
803
- // Display the dialog box.
804
- $modal.addClass('active');
805
- $body.addClass('has-fs-modal');
806
-
807
- var
808
- $singleInstallDetails = $( evt.target ).parents( 'tr.fs-install-details' ),
809
- isSingleSiteActivation = ( $singleInstallDetails.length > 0 );
810
-
811
- $modal.toggleClass( 'is-single-site-activation', isSingleSiteActivation );
812
-
813
- singleBlogID = isSingleSiteActivation ?
814
- $singleInstallDetails.prev().data( 'blog-id' ) :
815
- null;
816
-
817
- <?php if ( $fs->apply_filters( 'enable_per_site_activation', true ) ) : ?>
818
- $multisiteOptionsContainer.toggle( isNetworkActivation && ! isSingleSiteActivation );
819
- <?php endif ?>
820
-
821
- if ( hasLicenseTypes ) {
822
- $licenseTypes.attr( 'checked', false );
823
-
824
- if ( hasLicensesDropdown ) {
825
- $licensesDropdown.find( 'option:first' ).attr( 'selected', true ).trigger( 'change' );
826
- } else {
827
- $licenseTypes.filter( '[value="available"]' ).click();
828
- }
829
-
830
- $otherLicenseKey.val( '' );
831
- } else {
832
- $licenseKeyInput.val( '' );
833
- $licenseKeyInput.focus();
834
- }
835
- }
836
-
837
- function closeModal() {
838
- $modal.removeClass('active');
839
- $body.removeClass('has-fs-modal');
840
- }
841
-
842
- function resetActivateLicenseButton() {
843
- enableActivateLicenseButton();
844
- $activateLicenseButton.text( <?php echo json_encode( $activate_button_text ) ?> );
845
- }
846
-
847
- function resetModal() {
848
- hideError();
849
- resetActivateLicenseButton();
850
- }
851
-
852
- function enableActivateLicenseButton() {
853
- $activateLicenseButton.removeClass( 'disabled' );
854
- }
855
-
856
- function disableActivateLicenseButton() {
857
- $activateLicenseButton.addClass( 'disabled' );
858
- }
859
-
860
- function hideError() {
861
- $licenseActivationMessage.hide();
862
- }
863
-
864
- function showError( msg ) {
865
- $licenseActivationMessage.find( ' > p' ).html( msg );
866
- $licenseActivationMessage.show();
867
- }
868
- });
869
- })( jQuery );
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.9
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
+
22
+ $cant_find_license_key_text = fs_text_inline( "Can't find your license key?", 'cant-find-license-key', $slug );
23
+ $message_above_input_field = fs_text_inline( 'Please enter the license key that you received in the email right after the purchase:', 'activate-license-message', $slug );
24
+ $message_below_input_field = '';
25
+
26
+ $header_title = $fs->is_free_plan() ?
27
+ fs_text_inline( 'Activate License', 'activate-license', $slug ) :
28
+ fs_text_inline( 'Update License', 'update-license', $slug );
29
+
30
+ if ( $fs->is_registered() ) {
31
+ $activate_button_text = $header_title;
32
+ } else {
33
+ $freemius_site_url = $fs->has_paid_plan() ?
34
+ 'https://freemius.com/' :
35
+ // Insights platform information.
36
+ $fs->get_usage_tracking_terms_url();
37
+
38
+ $freemius_link = '<a href="' . $freemius_site_url . '" target="_blank" rel="noopener" tabindex="0">freemius.com</a>';
39
+
40
+ $message_below_input_field = sprintf(
41
+ fs_text_inline( '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.', 'license-sync-disclaimer', $slug ),
42
+ $fs->get_module_label( true ),
43
+ $freemius_link
44
+ );
45
+
46
+ $activate_button_text = fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug );
47
+ }
48
+
49
+ $license_key_text = fs_text_inline( 'License key', 'license-key' , $slug );
50
+
51
+ $is_network_activation = (
52
+ $fs->is_network_active() &&
53
+ fs_is_network_admin() &&
54
+ ! $fs->is_delegated_connection()
55
+ );
56
+ $network_activation_html = '';
57
+
58
+ $sites_details = array();
59
+ if ( $is_network_activation ) {
60
+ $all_sites = Freemius::get_sites();
61
+
62
+ foreach ( $all_sites as $site ) {
63
+ $site_details = $fs->get_site_info( $site );
64
+
65
+ $blog_id = Freemius::get_site_blog_id( $site );
66
+ $install = $fs->get_install_by_blog_id($blog_id);
67
+
68
+ if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) {
69
+ $site_details['license_id'] = $install->license_id;
70
+ }
71
+
72
+ $sites_details[] = $site_details;
73
+ }
74
+
75
+ if ( $is_network_activation ) {
76
+ $vars = array(
77
+ 'id' => $fs->get_id(),
78
+ 'sites' => $sites_details,
79
+ 'require_license_key' => true
80
+ );
81
+
82
+ $network_activation_html = fs_get_template( 'partials/network-activation.php', $vars );
83
+ }
84
+ }
85
+
86
+ $premium_licenses = $fs->get_available_premium_licenses();
87
+ $available_licenses = array();
88
+ foreach ( $premium_licenses as $premium_license ) {
89
+ $activations_left = $premium_license->left();
90
+ if ( ! ( $activations_left > 0 ) ) {
91
+ continue;
92
+ }
93
+
94
+ $available_licenses[ $activations_left . '_' . $premium_license->id ] = $premium_license;
95
+ }
96
+
97
+ $total_available_licenses = count( $available_licenses );
98
+ if ( $total_available_licenses > 0 ) {
99
+ $license_input_html = <<< HTML
100
+ <div class="fs-license-options-container">
101
+ <table>
102
+ <tbody>
103
+ <tr class="fs-available-license-key-container">
104
+ <td><input type="radio" name="license_type" value="available"></td>
105
+ <td>
106
+ HTML;
107
+
108
+ if ( $total_available_licenses > 1 ) {
109
+ // Sort the licenses by number of activations left in descending order.
110
+ krsort( $available_licenses );
111
+
112
+ $license_input_html .= '<select class="fs-licenses">';
113
+
114
+ /**
115
+ * @var FS_Plugin_License $license
116
+ */
117
+ foreach ( $available_licenses as $license ) {
118
+ $plan = $fs->_get_plan_by_id( $license->plan_id );
119
+
120
+ $label = sprintf(
121
+ "%s-Site %s License - %s",
122
+ ( 1 == $license->quota ?
123
+ 'Single' :
124
+ ( $license->is_unlimited() ? 'Unlimited' : $license->quota )
125
+ ),
126
+ ( is_object( $plan ) ? $plan->title : '' ),
127
+ $license->get_html_escaped_masked_secret_key()
128
+ );
129
+
130
+ $license_input_html .= "<option data-id='{$license->id}' value='{$license->secret_key}' data-left='{$license->left()}'>{$label}</option>";
131
+ }
132
+
133
+ $license_input_html .= '</select>';
134
+ } else {
135
+ $available_licenses = array_values( $available_licenses );
136
+
137
+ /**
138
+ * @var FS_Plugin_License $available_license
139
+ */
140
+ $available_license = $available_licenses[0];
141
+ $value = sprintf(
142
+ "%s-Site %s License - %s",
143
+ ( 1 == $available_license->quota ?
144
+ 'Single' :
145
+ ( $available_license->is_unlimited() ? 'Unlimited' : $available_license->quota )
146
+ ),
147
+ $fs->_get_plan_by_id( $available_license->plan_id )->title,
148
+ $available_license->get_html_escaped_masked_secret_key()
149
+ );
150
+
151
+ $license_input_html .= <<< HTML
152
+ <input
153
+ class="fs-available-license-key"
154
+ type="text"
155
+ value="{$value}"
156
+ data-id="{$available_license->id}"
157
+ data-license-key="{$available_license->secret_key}"
158
+ data-left="{$available_license->left()}"
159
+ readonly />
160
+ HTML;
161
+ }
162
+
163
+ $license_input_html .= <<< HTML
164
+ </td>
165
+ </tr>
166
+ <tr>
167
+ <td><input type="radio" name="license_type" value="other"></td>
168
+ <td class="fs-other-license-key-container">
169
+ <label for="other_license_key_{$unique_affix}">Other: </label>
170
+ <div>
171
+ <input id="other_license_key_{$unique_affix}" class="fs-license-key" type="text" placeholder="Enter license key" tabindex="1">
172
+ </div>
173
+ </td>
174
+ </tr>
175
+ </tbody>
176
+ </table>
177
+ </div>
178
+ HTML;
179
+ } else {
180
+ $license_input_html = "<input class='fs-license-key' type='text' placeholder='{$license_key_text}' tabindex='1' />";
181
+ }
182
+
183
+ $ownership_change_option_text = fs_text_inline( "Associate with the license owner's account.", 'associate-account-with-license-owner', $slug );
184
+ $ownership_change_option_html = "<div class='ownership-change-option-container' style='display: none'><label><input type='checkbox' /> <strong>{$ownership_change_option_text}</strong></label></div>";
185
+
186
+ /**
187
+ * IMPORTANT:
188
+ * DO NOT ADD MAXLENGTH OR LIMIT THE LICENSE KEY LENGTH SINCE
189
+ * WE DO WANT TO ALLOW INPUT OF LONGER KEYS (E.G. WooCommerce Keys)
190
+ * FOR MIGRATED MODULES.
191
+ */
192
+ $modal_content_html = <<< HTML
193
+ <div class="notice notice-error inline license-activation-message"><p></p></div>
194
+ <p>{$message_above_input_field}</p>
195
+ {$license_input_html}
196
+ <a class="show-license-resend-modal show-license-resend-modal-{$fs->get_unique_affix()}" href="!#" tabindex="2">{$cant_find_license_key_text}</a>
197
+ {$network_activation_html}
198
+ <p>{$message_below_input_field}</p>
199
+ {$ownership_change_option_html}
200
+ HTML;
201
+
202
+ /**
203
+ * Handle the ownership change option if not an add-on or if no license yet is activated for the
204
+ * parent product in case of an add-on.
205
+ *
206
+ * @author Leo Fajardo (@leorw)
207
+ * @since 2.3.2
208
+ */
209
+ $is_user_change_supported = ( ! $fs->is_addon() || ! $fs->get_parent_instance()->has_active_valid_license() );
210
+
211
+ fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
212
+ ?>
213
+ <script type="text/javascript">
214
+ (function( $ ) {
215
+ $( document ).ready(function() {
216
+ var modalContentHtml = <?php echo json_encode($modal_content_html); ?>,
217
+ modalHtml =
218
+ '<div class="fs-modal fs-modal-license-activation fs-modal-license-activation-<?php echo $unique_affix ?>">'
219
+ + ' <div class="fs-modal-dialog">'
220
+ + ' <div class="fs-modal-header">'
221
+ + ' <h4><?php echo esc_js($header_title) ?></h4>'
222
+ + ' <a href="!#" class="fs-close"><i class="dashicons dashicons-no" title="<?php echo esc_js( fs_text_x_inline( 'Dismiss', 'as close a window', 'dismiss', $slug ) ) ?>"></i></a>'
223
+ + ' </div>'
224
+ + ' <div class="fs-modal-body">'
225
+ + ' <div class="fs-modal-panel active">' + modalContentHtml + '</div>'
226
+ + ' </div>'
227
+ + ' <div class="fs-modal-footer">'
228
+ + ' <button class="button button-secondary button-close" tabindex="4"><?php fs_esc_js_echo_inline( 'Cancel', 'cancel', $slug ) ?></button>'
229
+ + ' <button class="button button-primary button-activate-license" tabindex="3"><?php echo esc_js( $activate_button_text ) ?></button>'
230
+ + ' </div>'
231
+ + ' </div>'
232
+ + '</div>',
233
+ $modal = $(modalHtml),
234
+ $activateLicenseButton = $modal.find('.button-activate-license'),
235
+ $licenseKeyInput = $modal.find( 'input.fs-license-key' ),
236
+ $licenseActivationMessage = $modal.find( '.license-activation-message' ),
237
+ isNetworkActivation = <?php echo $is_network_activation ? 'true' : 'false' ?>,
238
+ isUserChangeSupported = <?php echo $is_user_change_supported ? 'true' : 'false' ?>,
239
+ isSingleSiteActivation = false,
240
+ $ownershipChangeOptionContainer = $modal.find( '.ownership-change-option-container' ),
241
+ $body = $( 'body' );
242
+
243
+ $modal.appendTo( $body );
244
+
245
+ var
246
+ $licensesDropdown = $modal.find( '.fs-licenses' ),
247
+ $licenseTypes = $modal.find( 'input[type="radio"][name="license_type"]' ),
248
+ $applyOnAllSites = $modal.find( '.fs-apply-on-all-sites-checkbox' ),
249
+ $sitesListContainer = $modal.find( '.fs-sites-list-container' ),
250
+ $availableLicenseKey = $modal.find( '.fs-available-license-key' ),
251
+ $otherLicenseKey = $modal.find( '#other_license_key_<?php echo $unique_affix ?>' ),
252
+ $multisiteOptionsContainer = $modal.find( '.fs-multisite-options-container' ),
253
+ $activationsLeft = null,
254
+ hasLicensesDropdown = ( $licensesDropdown.length > 0 ),
255
+ hasLicenseTypes = ( $licenseTypes.length > 0 ),
256
+ maxSitesListHeight = null,
257
+ totalSites = <?php echo count( $sites_details ) ?>,
258
+ singleBlogID = null;
259
+
260
+ var
261
+ previousLicenseKey = null,
262
+ otherLicenseOwnerID = null,
263
+ /**
264
+ * @author Leo Fajardo (@leorw)
265
+ * @since 2.3.2
266
+ */
267
+ resetLoadingMode = function () {
268
+ // Reset loading mode.
269
+ $activateLicenseButton.text( <?php echo json_encode( $activate_button_text ) ?> );
270
+ $activateLicenseButton.prop( 'disabled', false );
271
+ $( document.body ).css( { 'cursor': 'auto' } );
272
+ $( '.fs-loading' ).removeClass( 'fs-loading' );
273
+
274
+ console.log( 'resetLoadingMode - Primary button was enabled' );
275
+ },
276
+ /**
277
+ * @author Leo Fajardo (@leorw)
278
+ * @since 2.3.2
279
+ */
280
+ setLoadingMode = function () {
281
+ $( document.body ).css( { 'cursor': 'wait' } );
282
+ },
283
+ /**
284
+ * @author Leo Fajardo (@leorw)
285
+ * @since 2.3.2
286
+ */
287
+ afterLicenseUserDataLoaded = function () {
288
+ if (
289
+ null !== otherLicenseOwnerID &&
290
+ otherLicenseOwnerID != <?php echo $fs->is_registered() ? $fs->get_user()->id : 'null' ?>
291
+ ) {
292
+ $ownershipChangeOptionContainer.show();
293
+ } else {
294
+ $ownershipChangeOptionContainer.hide();
295
+ $activateLicenseButton.focus();
296
+ }
297
+ },
298
+ /**
299
+ * @author Leo Fajardo (@leorw)
300
+ * @since 2.3.2
301
+ */
302
+ fetchLicenseUserData = function () {
303
+ var hideAndUncheckUserChangeCheckbox = ( ! isUserChangeSupported ),
304
+ otherLicenseKeyIsSelected = isOtherLicenseKeySelected();
305
+
306
+ if ( ! hideAndUncheckUserChangeCheckbox ) {
307
+ // User change is supported only on the site level.
308
+ hideAndUncheckUserChangeCheckbox = ( isNetworkActivation || isSingleSiteActivation );
309
+ }
310
+
311
+ if ( ! hideAndUncheckUserChangeCheckbox ) {
312
+ hideAndUncheckUserChangeCheckbox = ( hasLicenseTypes && ! otherLicenseKeyIsSelected );
313
+ }
314
+
315
+ var licenseKey = $licenseKeyInput.val().trim();
316
+
317
+ if ( ! hideAndUncheckUserChangeCheckbox && otherLicenseKeyIsSelected ) {
318
+ hideAndUncheckUserChangeCheckbox = ( licenseKey.length < 32 );
319
+ }
320
+
321
+ if ( licenseKey !== previousLicenseKey ) {
322
+ // If the license key has not been changed, keep the owner ID in order to prevent another API call.
323
+ otherLicenseOwnerID = null;
324
+ }
325
+
326
+ if ( hideAndUncheckUserChangeCheckbox ) {
327
+ $ownershipChangeOptionContainer.hide().find( 'input' ).attr( 'checked', false );
328
+
329
+ return;
330
+ }
331
+
332
+ if ( null !== otherLicenseOwnerID ) {
333
+ afterLicenseUserDataLoaded();
334
+ return;
335
+ }
336
+
337
+ setLoadingMode();
338
+
339
+ $activateLicenseButton.addClass( 'fs-loading' );
340
+ $activateLicenseButton.attr( 'disabled', 'disabled' );
341
+ $activateLicenseButton.html( '<?php fs_esc_js_echo_inline( 'Please wait', 'please-wait', $slug ) ?>...' );
342
+
343
+ $.ajax( {
344
+ url : ajaxurl,
345
+ method : 'POST',
346
+ data : {
347
+ action : '<?php echo $fs->get_ajax_action( 'fetch_is_marketing_required_flag_value' ) ?>',
348
+ security : '<?php echo $fs->get_ajax_security( 'fetch_is_marketing_required_flag_value' ) ?>',
349
+ license_key: licenseKey,
350
+ module_id : '<?php echo $fs->get_id() ?>'
351
+ },
352
+ success: function ( result ) {
353
+ resetLoadingMode();
354
+
355
+ if ( result.success ) {
356
+ result = result.data;
357
+
358
+ // Cache license owner's ID.
359
+ otherLicenseOwnerID = result.license_owner_id;
360
+ }
361
+
362
+ afterLicenseUserDataLoaded();
363
+ }
364
+ } );
365
+ };
366
+
367
+ function registerEventHandlers() {
368
+ var
369
+ $otherLicenseKeyContainer = $modal.find( '.fs-other-license-key-container' );
370
+
371
+ if ( isNetworkActivation ) {
372
+ $applyOnAllSites.click(function() {
373
+ var applyOnAllSites = $( this ).is( ':checked' );
374
+
375
+ $multisiteOptionsContainer.toggleClass( 'fs-apply-on-all-sites', applyOnAllSites );
376
+
377
+ showSites( ! applyOnAllSites );
378
+
379
+ if ( hasValidLicenseKey() && ( applyOnAllSites || hasSelectedSite() ) ) {
380
+ enableActivateLicenseButton();
381
+ } else {
382
+ disableActivateLicenseButton();
383
+ }
384
+ });
385
+
386
+ $sitesListContainer.delegate( 'td:not(:first-child)', 'click', function() {
387
+ // If a site row is clicked, trigger a click on the checkbox.
388
+ $( this ).parent().find( 'td:first-child input' ).click();
389
+ });
390
+
391
+ $sitesListContainer.delegate( 'input[type="checkbox"]', 'click', function() {
392
+ enableDisableSitesSelection();
393
+
394
+ if ( hasValidLicenseKey() && hasSelectedSite() ) {
395
+ enableActivateLicenseButton();
396
+ } else {
397
+ disableActivateLicenseButton();
398
+ }
399
+ });
400
+ }
401
+
402
+ if ( hasLicensesDropdown ) {
403
+ $licensesDropdown.change(function() {
404
+ // When a license is selected, select the associated radio button.
405
+ $licenseTypes.filter( '[value="available"]' ).attr( 'checked', true );
406
+
407
+ if ( ! isNetworkActivation || $modal.hasClass( 'is-single-site-activation' ) ) {
408
+ enableActivateLicenseButton();
409
+ return true;
410
+ }
411
+
412
+ toggleActivationOnAllSites();
413
+ })
414
+ }
415
+
416
+ if ( hasLicenseTypes ) {
417
+ $licenseTypes.change(function() {
418
+ var
419
+ licenseKey = $modal.find( 'input.fs-license-key' ).val().trim(),
420
+ otherLicenseKeySelected = isOtherLicenseKeySelected();
421
+
422
+ if ( ( licenseKey.length > 0 || ( hasLicenseTypes && ! otherLicenseKeySelected ) ) &&
423
+ ( $modal.hasClass( 'is-single-site-activation' ) || ! isNetworkActivation || hasSelectedSite() )
424
+ ) {
425
+ /**
426
+ * If the "other" license is not empty or an available license is selected, enable the activate
427
+ * button.
428
+ *
429
+ * @author Leo Fajardo (@leorw)
430
+ */
431
+ enableActivateLicenseButton();
432
+ } else {
433
+ disableActivateLicenseButton();
434
+ }
435
+
436
+ if ( '' !== licenseKey ) {
437
+ fetchLicenseUserData();
438
+ }
439
+
440
+ if ( ! isNetworkActivation ) {
441
+ return;
442
+ }
443
+
444
+ if ( otherLicenseKeySelected ) {
445
+ $applyOnAllSites.attr( 'disabled', false );
446
+ enableDisableSitesSelection();
447
+ resetActivateLicenseCheckboxLabel();
448
+ } else if ( ! $modal.hasClass( 'is-single-site-activation' ) ) {
449
+ toggleActivationOnAllSites();
450
+ }
451
+ });
452
+
453
+ if ( ! hasLicensesDropdown ) {
454
+ $availableLicenseKey.click(function() {
455
+ $licenseTypes.filter( '[value="available"]' ).click();
456
+ });
457
+ }
458
+
459
+ $otherLicenseKeyContainer.click(function() {
460
+ $licenseTypes.filter( '[value="other"]' ).click();
461
+ });
462
+ }
463
+
464
+ $body.on( 'click', 'span.activate-license.<?php echo $unique_affix ?> a, .activate-license-trigger.<?php echo $unique_affix ?>', function (evt) {
465
+ evt.preventDefault();
466
+
467
+ showModal( evt );
468
+ });
469
+
470
+ var licenseTimeout = null;
471
+
472
+ /**
473
+ * Disable activation button when license key is empty.
474
+ *
475
+ * @author Leo Fajardo (@leorw)
476
+ * @since 2.3.2
477
+ */
478
+ $modal.on( 'keyup paste delete cut', 'input.fs-license-key', function () {
479
+ clearTimeout(licenseTimeout);
480
+
481
+ licenseTimeout = setTimeout( function () {
482
+ var licenseKey = $licenseKeyInput.val().trim();
483
+
484
+ if ( licenseKey == previousLicenseKey ) {
485
+ afterLicenseUserDataLoaded();
486
+ return;
487
+ }
488
+
489
+ if ( '' === licenseKey ) {
490
+ disableActivateLicenseButton();
491
+ $ownershipChangeOptionContainer.hide();
492
+ } else {
493
+ enableActivateLicenseButton();
494
+
495
+ if ( 32 <= licenseKey.length ) {
496
+ fetchLicenseUserData();
497
+ } else {
498
+ $ownershipChangeOptionContainer.hide();
499
+ }
500
+ }
501
+
502
+ previousLicenseKey = licenseKey;
503
+ }, 200 );
504
+ } ).focus();
505
+
506
+ $modal.on('input propertychange', 'input.fs-license-key', function () {
507
+
508
+ var licenseKey = $(this).val().trim();
509
+
510
+ /**
511
+ * If license key is not empty, enable the license activation button.
512
+ */
513
+ if ( licenseKey.length > 0 && ( $modal.hasClass( 'is-single-site-activation' ) || ! isNetworkActivation || hasSelectedSite() ) ) {
514
+ enableActivateLicenseButton();
515
+ }
516
+ });
517
+
518
+ $modal.on( 'blur', 'input.fs-license-key', function( evt ) {
519
+ var
520
+ licenseKey = $(this).val().trim(),
521
+ $focusedElement = $( evt.relatedTarget ),
522
+ hasSelectedAvailableLicense = ( hasLicenseTypes && $focusedElement.parents( '.fs-available-license-key-container' ).length > 0 );
523
+
524
+ /**
525
+ * If license key is empty, disable the license activation button.
526
+ */
527
+ if ( ( 0 === licenseKey.length && ( ! hasLicenseTypes || ! hasSelectedAvailableLicense ) ) ||
528
+ ( isNetworkActivation && ! hasSelectedSite() )
529
+ ) {
530
+ disableActivateLicenseButton();
531
+ }
532
+ });
533
+
534
+ $modal.on('click', '.button-activate-license', function (evt) {
535
+ evt.preventDefault();
536
+
537
+ if ($(this).hasClass('disabled')) {
538
+ return;
539
+ }
540
+
541
+ var
542
+ licenseKey = '';
543
+
544
+ if ( hasLicenseTypes ) {
545
+ if ( isOtherLicenseKeySelected() ) {
546
+ licenseKey = $otherLicenseKey.val();
547
+ } else {
548
+ if ( ! hasLicensesDropdown ) {
549
+ licenseKey = $availableLicenseKey.data( 'license-key' );
550
+ } else {
551
+ licenseKey = $licensesDropdown.val();
552
+ }
553
+ }
554
+ } else {
555
+ licenseKey = $licenseKeyInput.val().trim();
556
+ }
557
+
558
+ disableActivateLicenseButton();
559
+
560
+ if (0 === licenseKey.length) {
561
+ return;
562
+ }
563
+
564
+ var data = {
565
+ action : '<?php echo $fs->get_ajax_action( 'activate_license' ) ?>',
566
+ security : '<?php echo $fs->get_ajax_security( 'activate_license' ) ?>',
567
+ license_key: licenseKey,
568
+ module_id : '<?php echo $fs->get_id() ?>'
569
+ };
570
+
571
+ if ( isNetworkActivation ) {
572
+ var
573
+ sites = [];
574
+
575
+ if ( null === singleBlogID ) {
576
+ var
577
+ applyOnAllSites = $applyOnAllSites.is( ':checked' );
578
+
579
+ $sitesListContainer.find( 'tr' ).each(function() {
580
+ var
581
+ $this = $( this ),
582
+ includeSite = ( applyOnAllSites || $this.find( 'input' ).is( ':checked' ) );
583
+
584
+ if ( ! includeSite )
585
+ return;
586
+
587
+ var site = {
588
+ uid : $this.find( '.uid' ).val(),
589
+ url : $this.find( '.url' ).val(),
590
+ title : $this.find( '.title' ).val(),
591
+ language: $this.find( '.language' ).val(),
592
+ charset : $this.find( '.charset' ).val(),
593
+ blog_id : $this.find( '.blog-id' ).find( 'span' ).text()
594
+ };
595
+
596
+ sites.push( site );
597
+ });
598
+ } else {
599
+ data.blog_id = singleBlogID;
600
+ }
601
+
602
+ data.sites = sites;
603
+ }
604
+
605
+ if ( $ownershipChangeOptionContainer.find( 'input:checked' ).length > 0 ) {
606
+ data.user_id = otherLicenseOwnerID;
607
+ }
608
+
609
+ $.ajax({
610
+ url: ajaxurl,
611
+ method: 'POST',
612
+ data: data,
613
+ beforeSend: function () {
614
+ $activateLicenseButton.text( '<?php fs_esc_js_echo_inline( 'Activating license', 'activating-license', $slug ) ?>...' );
615
+ },
616
+ success: function( result ) {
617
+ var resultObj = $.parseJSON( result );
618
+ if ( resultObj.success ) {
619
+ closeModal();
620
+
621
+ // Redirect to the "Account" page and sync the license.
622
+ window.location.href = resultObj.next_page;
623
+ } else {
624
+ showError( resultObj.error.message ? resultObj.error.message : resultObj.error );
625
+ resetActivateLicenseButton();
626
+ }
627
+ }
628
+ });
629
+ });
630
+
631
+ // If the user has clicked outside the window, close the modal.
632
+ $modal.on('click', '.fs-close, .button-secondary', function () {
633
+ closeModal();
634
+ return false;
635
+ });
636
+ }
637
+
638
+ registerEventHandlers();
639
+
640
+ $body.trigger('licenseActivationLoaded');
641
+
642
+ /**
643
+ * @author Leo Fajardo (@leorw)
644
+ * @since 2.0.0
645
+ */
646
+ function enableDisableSitesSelection() {
647
+ var
648
+ canApplyOnAllSites = $applyOnAllSites.is( ':enabled' ),
649
+ disableSitesSelection = null;
650
+
651
+ if ( ! canApplyOnAllSites ) {
652
+ var
653
+ selectedSites = $sitesListContainer.find( 'input[type="checkbox"]:checked' ).length,
654
+ activationsLeft = Math.max( 0, $activationsLeft.data( 'left' ) - selectedSites );
655
+
656
+ disableSitesSelection = ( 0 === activationsLeft );
657
+
658
+ $activationsLeft.text( activationsLeft );
659
+ } else {
660
+ disableSitesSelection = false;
661
+ }
662
+
663
+ $sitesListContainer
664
+ .find( 'input[type="checkbox"]:not(:checked)' )
665
+ .attr( 'disabled', disableSitesSelection );
666
+ }
667
+
668
+ /**
669
+ * @author Leo Fajardo (@leorw)
670
+ * @since 2.0.0
671
+ *
672
+ * @returns {Boolean}
673
+ */
674
+ function isOtherLicenseKeySelected() {
675
+ return ( hasLicenseTypes && 'other' === $licenseTypes.filter( ':checked' ).val() );
676
+ }
677
+
678
+ /**
679
+ * @author Leo Fajardo (@leorw)
680
+ * @since 2.0.0
681
+ *
682
+ * @returns {Boolean}
683
+ */
684
+ function hasValidLicenseKey() {
685
+ var licenseKey = '';
686
+ if ( hasLicenseTypes ) {
687
+ if ( 'available' === $licenseTypes.filter( ':checked' ).val() ) {
688
+ return true;
689
+ } else {
690
+ licenseKey = $otherLicenseKey.val();
691
+ }
692
+ } else {
693
+ licenseKey = $modal.find( 'input.fs-license-key' ).val();
694
+ }
695
+
696
+ return ( licenseKey.trim().length > 0 );
697
+ }
698
+
699
+ /**
700
+ * @author