GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( bb6361...a2a740 )
by Brad
04:28
created

Freemius::__construct()   C

Complexity

Conditions 11
Paths 32

Size

Total Lines 68
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 37
nc 32
nop 3
dl 0
loc 68
rs 5.8371
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 13 and the first side effect is on line 9.

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

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

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

Loading history...
2
	/**
3
	 * @package     Freemius
4
	 * @copyright   Copyright (c) 2015, Freemius, Inc.
5
	 * @license     https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
	 * @since       1.0.3
7
	 */
8
	if ( ! defined( 'ABSPATH' ) ) {
9
		exit;
10
	}
11
12
	// "final class"
13
	class Freemius extends Freemius_Abstract {
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
14
		/**
15
		 * SDK Version
16
		 *
17
		 * @var string
18
		 */
19
		public $version = WP_FS__SDK_VERSION;
20
21
		#region Plugin Info
22
23
		/**
24
		 * @since 1.0.1
25
		 *
26
		 * @var string
27
		 */
28
		private $_slug;
29
30
		/**
31
		 * @since 1.0.0
32
		 *
33
		 * @var string
34
		 */
35
		private $_plugin_basename;
36
		/**
37
		 * @since 1.0.0
38
		 *
39
		 * @var string
40
		 */
41
		private $_free_plugin_basename;
42
		/**
43
		 * @since 1.0.0
44
		 *
45
		 * @var string
46
		 */
47
		private $_plugin_dir_path;
48
		/**
49
		 * @since 1.0.0
50
		 *
51
		 * @var string
52
		 */
53
		private $_plugin_dir_name;
54
		/**
55
		 * @since 1.0.0
56
		 *
57
		 * @var string
58
		 */
59
		private $_plugin_main_file_path;
60
		/**
61
		 * @var string[]
62
		 */
63
		private $_plugin_data;
64
		/**
65
		 * @since 1.0.9
66
		 *
67
		 * @var string
68
		 */
69
		private $_plugin_name;
70
		/**
71
		 * @since 1.2.2
72
		 *
73
		 * @var string
74
		 */
75
		private $_module_type;
76
77
		#endregion Plugin Info
78
79
		/**
80
		 * @since 1.0.9
81
		 *
82
		 * @var bool If false, don't turn Freemius on.
83
		 */
84
		private $_is_on;
85
86
		/**
87
		 * @since 1.1.3
88
		 *
89
		 * @var bool If false, don't turn Freemius on.
90
		 */
91
		private $_is_anonymous;
92
93
		/**
94
		 * @since 1.0.9
95
		 * @var bool If false, issues with connectivity to Freemius API.
96
		 */
97
		private $_has_api_connection;
98
99
		/**
100
		 * @since 1.0.9
101
		 * @var bool Hints the SDK if plugin can support anonymous mode (if skip connect is visible).
102
		 */
103
		private $_enable_anonymous;
104
105
		/**
106
		 * @since 1.1.7.5
107
		 * @var bool Hints the SDK if plugin should run in anonymous mode (only adds feedback form).
108
		 */
109
		private $_anonymous_mode;
110
111
		/**
112
		 * @since 1.1.9
113
		 * @var bool Hints the SDK if plugin have any free plans.
114
		 */
115
		private $_is_premium_only;
116
117
		/**
118
		 * @since 1.2.1.6
119
		 * @var bool Hints the SDK if plugin have premium code version at all.
120
		 */
121
		private $_has_premium_version;
122
123
		/**
124
		 * @since 1.2.1.6
125
		 * @var bool Hints the SDK if plugin should ignore pending mode by simulating a skip.
126
		 */
127
		private $_ignore_pending_mode;
128
129
		/**
130
		 * @since 1.0.8
131
		 * @var bool Hints the SDK if the plugin has any paid plans.
132
		 */
133
		private $_has_paid_plans;
134
135
		/**
136
		 * @since 1.2.1.5
137
		 * @var int Hints the SDK if the plugin offers a trial period. If negative, no trial, if zero - has a trial but
138
		 *      without a specified period, if positive - the number of trial days.
139
		 */
140
		private $_trial_days = - 1;
141
142
		/**
143
		 * @since 1.2.1.5
144
		 * @var bool Hints the SDK if the trial requires a payment method or not.
145
		 */
146
		private $_is_trial_require_payment = false;
147
148
		/**
149
		 * @since 1.0.7
150
		 * @var bool Hints the SDK if the plugin is WordPress.org compliant.
151
		 */
152
		private $_is_org_compliant;
153
154
		/**
155
		 * @since 1.0.7
156
		 * @var bool Hints the SDK if the plugin is has add-ons.
157
		 */
158
		private $_has_addons;
159
160
		/**
161
		 * @since 1.1.6
162
		 * @var string[]bool.
163
		 */
164
		private $_permissions;
165
166
		/**
167
		 * @var FS_Key_Value_Storage
168
		 */
169
		private $_storage;
170
171
		/**
172
		 * @since 1.2.2.7
173
		 * @var FS_Cache_Manager
174
		 */
175
		private $_cache;
176
177
		/**
178
		 * @since 1.0.0
179
		 *
180
		 * @var FS_Logger
181
		 */
182
		private $_logger;
183
		/**
184
		 * @since 1.0.4
185
		 *
186
		 * @var FS_Plugin
187
		 */
188
		private $_plugin = false;
189
		/**
190
		 * @since 1.0.4
191
		 *
192
		 * @var FS_Plugin|false
193
		 */
194
		private $_parent_plugin = false;
195
		/**
196
		 * @since 1.1.1
197
		 *
198
		 * @var Freemius
199
		 */
200
		private $_parent = false;
201
		/**
202
		 * @since 1.0.1
203
		 *
204
		 * @var FS_User
205
		 */
206
		private $_user = false;
207
		/**
208
		 * @since 1.0.1
209
		 *
210
		 * @var FS_Site
211
		 */
212
		private $_site = false;
213
		/**
214
		 * @since 1.0.1
215
		 *
216
		 * @var FS_Plugin_License
217
		 */
218
		private $_license;
219
		/**
220
		 * @since 1.0.2
221
		 *
222
		 * @var FS_Plugin_Plan[]
223
		 */
224
		private $_plans = false;
225
		/**
226
		 * @var FS_Plugin_License[]
227
		 * @since 1.0.5
228
		 */
229
		private $_licenses = false;
230
231
		/**
232
		 * @since 1.0.1
233
		 *
234
		 * @var FS_Admin_Menu_Manager
235
		 */
236
		private $_menu;
237
238
		/**
239
		 * @var FS_Admin_Notice_Manager
240
		 */
241
		private $_admin_notices;
242
243
		/**
244
		 * @since 1.1.6
245
		 *
246
		 * @var FS_Admin_Notice_Manager
247
		 */
248
		private static $_global_admin_notices;
249
250
		/**
251
		 * @var FS_Logger
252
		 * @since 1.0.0
253
		 */
254
		private static $_static_logger;
255
256
		/**
257
		 * @var FS_Option_Manager
258
		 * @since 1.0.2
259
		 */
260
		private static $_accounts;
261
262
		/**
263
		 * @since 1.2.2
264
		 *
265
		 * @var number
266
		 */
267
		private $_module_id;
268
269
		/**
270
		 * @var Freemius[]
271
		 */
272
		private static $_instances = array();
273
274
        /**
275
         * @author Leo Fajardo (@leorw)
276
         *
277
         * @since 1.2.3
278
         *
279
         * @var FS_Affiliate
280
         */
281
        private $affiliate = null;
282
283
        /**
284
         * @author Leo Fajardo (@leorw)
285
         *
286
         * @since 1.2.3
287
         *
288
         * @var FS_AffiliateTerms
289
         */
290
        private $plugin_affiliate_terms = null;
291
292
        /**
293
         * @author Leo Fajardo (@leorw)
294
         *
295
         * @since 1.2.3
296
         *
297
         * @var FS_AffiliateTerms
298
         */
299
        private $custom_affiliate_terms = null;
300
301
		#region Uninstall Reasons IDs
302
303
		const REASON_NO_LONGER_NEEDED = 1;
304
		const REASON_FOUND_A_BETTER_PLUGIN = 2;
305
		const REASON_NEEDED_FOR_A_SHORT_PERIOD = 3;
306
		const REASON_BROKE_MY_SITE = 4;
307
		const REASON_SUDDENLY_STOPPED_WORKING = 5;
308
		const REASON_CANT_PAY_ANYMORE = 6;
309
		const REASON_OTHER = 7;
310
		const REASON_DIDNT_WORK = 8;
311
		const REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION = 9;
312
		const REASON_COULDNT_MAKE_IT_WORK = 10;
313
		const REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE = 11;
314
		const REASON_NOT_WORKING = 12;
315
		const REASON_NOT_WHAT_I_WAS_LOOKING_FOR = 13;
316
		const REASON_DIDNT_WORK_AS_EXPECTED = 14;
317
		const REASON_TEMPORARY_DEACTIVATION = 15;
318
319
		#endregion
320
321
		/* Ctor
322
------------------------------------------------------------------------------------------------------------------*/
323
324
		/**
325
		 * Main singleton instance.
326
		 *
327
		 * @author Vova Feldman (@svovaf)
328
		 * @since  1.0.0
329
		 *
330
		 * @param number      $module_id
331
		 * @param string|bool $slug
332
		 * @param bool        $is_init Since 1.2.1 Is initiation sequence.
333
		 */
334
		private function __construct( $module_id, $slug = false, $is_init = false ) {
335
			if ( $is_init && is_numeric( $module_id ) && is_string( $slug ) ) {
336
				$this->store_id_slug_type_path_map( $module_id, $slug );
337
			}
338
339
			$this->_module_id   = $module_id;
0 ignored issues
show
Documentation Bug introduced by
It seems like $module_id can also be of type string. However, the property $_module_id is declared as type integer|double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
340
			$this->_slug        = $this->get_slug();
341
			$this->_module_type = $this->get_module_type();
342
343
			$this->_storage = FS_Key_Value_Storage::instance( $this->_module_type . '_data', $this->_slug );
344
			$this->_cache   = FS_Cache_Manager::get_manager( WP_FS___OPTION_PREFIX . "cache_{$module_id}" );
345
346
			$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->get_unique_affix(), WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
347
348
			$this->_plugin_main_file_path = $this->_find_caller_plugin_file( $is_init );
349
			$this->_plugin_dir_path       = plugin_dir_path( $this->_plugin_main_file_path );
350
			$this->_plugin_basename       = $this->get_plugin_basename();
351
			$this->_free_plugin_basename  = str_replace( '-premium/', '/', $this->_plugin_basename );
352
353
			$base_name_split        = explode( '/', $this->_plugin_basename );
354
			$this->_plugin_dir_name = $base_name_split[0];
355
356
			if ( $this->_logger->is_on() ) {
357
				$this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path );
358
				$this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path );
359
				$this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename );
360
				$this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename );
361
				$this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name );
362
			}
363
364
			// Remember link between file to slug.
365
			$this->store_file_slug_map();
366
367
			// Store plugin's initial install timestamp.
368
			if ( ! isset( $this->_storage->install_timestamp ) ) {
369
				$this->_storage->install_timestamp = WP_FS__SCRIPT_START_TIME;
370
			}
371
372
			if ( ! is_object( $this->_plugin ) ) {
373
				$this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->get();
0 ignored issues
show
Documentation Bug introduced by
It seems like \FS_Plugin_Manager::inst...his->_module_id)->get() can also be of type false. However, the property $_plugin is declared as type object<FS_Plugin>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
374
			}
375
376
			$this->_admin_notices = FS_Admin_Notice_Manager::instance(
377
				$this->_slug . ( $this->is_theme() ? ':theme' : '' ),
378
				/**
379
				 * Ensure that the admin notice will always have a title by using the stored plugin title if available and
380
				 * retrieving the title via the "get_plugin_name" method if there is no stored plugin title available.
381
				 *
382
				 * @author Leo Fajardo (@leorw)
383
				 * @since  1.2.2
384
				 */
385
				( is_object( $this->_plugin ) ? $this->_plugin->title : $this->get_plugin_name() ),
386
				$this->get_unique_affix()
387
			);
388
389
			if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) ||
390
			     'true' === fs_request_is_action( 'restart_freemius' )
391
			) {
392
				FS_Api::clear_cache();
393
				$this->_cache->clear();
394
			}
395
396
			$this->_register_hooks();
397
398
			$this->_load_account();
399
400
			$this->_version_updates_handler();
401
		}
402
403
		/**
404
		 * Checks whether this module has a settings menu.
405
		 *
406
		 * @author Leo Fajardo (@leorw)
407
		 * @since  1.2.2
408
		 *
409
		 * @return bool
410
		 */
411
		function has_settings_menu() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
412
			return $this->_menu->has_menu();
413
		}
414
415
		/**
416
		 * Check if the context module is free wp.org theme.
417
		 *
418
		 * This method is helpful because:
419
		 *      1. wp.org themes are limited to a single submenu item,
420
		 *         and sub-submenu items are most likely not allowed (never verified).
421
		 *      2. wp.org themes are not allowed to redirect the user
422
		 *         after the theme activation, therefore, the agreed UX
423
		 *         is showing the opt-in as a modal dialog box after
424
		 *         activation (approved by @otto42, @emiluzelac, @greenshady, @grapplerulrich).
425
		 *
426
		 * @author Vova Feldman (@svovaf)
427
		 * @since  1.2.2.7
428
		 *
429
		 * @return bool
430
		 */
431
		function is_free_wp_org_theme() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
432
			return (
433
				$this->is_theme() &&
434
				$this->is_org_repo_compliant() &&
435
				! $this->is_premium()
436
			);
437
		}
438
439
		/**
440
		 * Checks whether this a submenu item is visible.
441
		 *
442
		 * @author Vova Feldman (@svovaf)
443
		 * @since  1.2.2.6
444
		 * @since  1.2.2.7 Even if the menu item was specified to be hidden, when it is the context page, then show the submenu item so the user will have the right context page.
445
		 *
446
		 * @param string $slug
447
		 *
448
		 * @return bool
449
		 */
450
		function is_submenu_item_visible( $slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
451
			if ( $this->is_admin_page( $slug ) ) {
452
				/**
453
				 * It is the current context page, so show the submenu item
454
				 * so the user will have the right context page, even if it
455
				 * was set to hidden.
456
				 */
457
				return true;
458
			}
459
460
			if ( ! $this->has_settings_menu() ) {
461
				// No menu settings at all.
462
				return false;
463
			}
464
465
			if ( $this->is_free_wp_org_theme() ) {
466
				/**
467
				 * wp.org themes are limited to a single submenu item, and
468
				 * sub-submenu items are most likely not allowed (never verified).
469
				 */
470
				return false;
471
			}
472
473
			return $this->_menu->is_submenu_item_visible( $slug );
474
		}
475
476
		/**
477
		 * Check if a Freemius page should be accessible via the UI.
478
		 *
479
		 * @author Vova Feldman (@svovaf)
480
		 * @since  1.2.2.7
481
		 *
482
		 * @param string $slug
483
		 *
484
		 * @return bool
485
		 */
486
		function is_page_visible( $slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
487
			if ( $this->is_admin_page( $slug ) ) {
488
				return true;
489
			}
490
491
			return $this->_menu->is_submenu_item_visible( $slug, true, true );
492
		}
493
494
		/**
495
		 * @author Vova Feldman (@svovaf)
496
		 * @since  1.0.9
497
		 */
498
		private function _version_updates_handler() {
499
			if ( ! isset( $this->_storage->sdk_version ) || $this->_storage->sdk_version != $this->version ) {
500
				// Freemius version upgrade mode.
501
				$this->_storage->sdk_last_version = $this->_storage->sdk_version;
502
				$this->_storage->sdk_version      = $this->version;
503
504
				if ( empty( $this->_storage->sdk_last_version ) ||
505
				     version_compare( $this->_storage->sdk_last_version, $this->version, '<' )
506
				) {
507
					$this->_storage->sdk_upgrade_mode   = true;
508
					$this->_storage->sdk_downgrade_mode = false;
509
				} else {
510
					$this->_storage->sdk_downgrade_mode = true;
511
					$this->_storage->sdk_upgrade_mode   = false;
512
513
				}
514
515
				$this->do_action( 'sdk_version_update', $this->_storage->sdk_last_version, $this->version );
516
			}
517
518
			$plugin_version = $this->get_plugin_version();
519
			if ( ! isset( $this->_storage->plugin_version ) || $this->_storage->plugin_version != $plugin_version ) {
520
				// Plugin version upgrade mode.
521
				$this->_storage->plugin_last_version = $this->_storage->plugin_version;
522
				$this->_storage->plugin_version      = $plugin_version;
523
524
				if ( empty( $this->_storage->plugin_last_version ) ||
525
				     version_compare( $this->_storage->plugin_last_version, $plugin_version, '<' )
526
				) {
527
					$this->_storage->plugin_upgrade_mode   = true;
528
					$this->_storage->plugin_downgrade_mode = false;
529
				} else {
530
					$this->_storage->plugin_downgrade_mode = true;
531
					$this->_storage->plugin_upgrade_mode   = false;
532
				}
533
534
				if ( ! empty( $this->_storage->plugin_last_version ) ) {
535
					// Different version of the plugin was installed before, therefore it's an update.
536
					$this->_storage->is_plugin_new_install = false;
537
				}
538
539
				$this->do_action( 'plugin_version_update', $this->_storage->plugin_last_version, $plugin_version );
540
			}
541
		}
542
543
		/**
544
		 * @author Vova Feldman (@svovaf)
545
		 * @since  1.1.5
546
		 *
547
		 * @param string $sdk_prev_version
548
		 * @param string $sdk_version
549
		 */
550
		function _data_migration( $sdk_prev_version, $sdk_version ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
551
			/**
552
			 * @since 1.1.7.3 Fixed unwanted connectivity test cleanup.
553
			 */
554
			if ( empty( $sdk_prev_version ) ) {
555
				return;
556
			}
557
558
			if ( version_compare( $sdk_prev_version, '1.1.5', '<' ) &&
559
			     version_compare( $sdk_version, '1.1.5', '>=' )
560
			) {
561
				// On version 1.1.5 merged connectivity and is_on data.
562
				if ( isset( $this->_storage->connectivity_test ) ) {
563
					if ( ! isset( $this->_storage->is_on ) ) {
564
						unset( $this->_storage->connectivity_test );
565
					} else {
566
						$connectivity_data              = $this->_storage->connectivity_test;
567
						$connectivity_data['is_active'] = $this->_storage->is_on['is_active'];
568
						$connectivity_data['timestamp'] = $this->_storage->is_on['timestamp'];
569
570
						// Override.
571
						$this->_storage->connectivity_test = $connectivity_data;
572
573
						// Remove previous structure.
574
						unset( $this->_storage->is_on );
575
					}
576
577
				}
578
			}
579
		}
580
581
		/**
582
		 * @author Vova Feldman (@svovaf)
583
		 * @since  1.2.2.7
584
		 *
585
		 * @param string $plugin_prev_version
586
		 * @param string $plugin_version
587
		 */
588
		function _after_version_update( $plugin_prev_version, $plugin_version ) {
0 ignored issues
show
Unused Code introduced by
The parameter $plugin_prev_version is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $plugin_version is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
589
			if ( $this->is_theme() ) {
590
				// Expire the cache of the previous tabs since the theme may
591
				// have setting updates.
592
				$this->_cache->expire( 'tabs' );
593
				$this->_cache->expire( 'tabs_stylesheets' );
594
			}
595
		}
596
597
		/**
598
		 * This action is connected to the 'plugins_loaded' hook and helps to determine
599
		 * if this is a new plugin installation or a plugin update.
600
		 *
601
		 * There are 3 different use-cases:
602
		 *    1) New plugin installation right with Freemius:
603
		 *       1.1 _activate_plugin_event_hook() will be executed first
604
		 *       1.2 Since $this->_storage->is_plugin_new_install is not set,
605
		 *           and $this->_storage->plugin_last_version is not set,
606
		 *           $this->_storage->is_plugin_new_install will be set to TRUE.
607
		 *       1.3 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
608
		 *           be already set to TRUE.
609
		 *
610
		 *    2) Plugin update, didn't have Freemius before, and now have the SDK:
611
		 *       2.1 _activate_plugin_event_hook() will not be executed, because
612
		 *           the activation hook do NOT fires on updates since WP 3.1.
613
		 *       2.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
614
		 *           be empty, therefore, it will be set to FALSE.
615
		 *
616
		 *    3) Plugin update, had Freemius in prev version as well:
617
		 *       3.1 _version_updates_handler() will be executed 1st, since FS was installed
618
		 *           before, $this->_storage->plugin_last_version will NOT be empty,
619
		 *           therefore, $this->_storage->is_plugin_new_install will be set to FALSE.
620
		 *       3.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install is
621
		 *           already set, therefore, it will not be modified.
622
		 *
623
		 *    Use-case #3 is backward compatible, #3.1 will be executed since 1.0.9.
624
		 *
625
		 * NOTE:
626
		 *    The only fallback of this mechanism is if an admin updates a plugin based on use-case #2,
627
		 *    and then, the next immediate PageView is the plugin's main settings page, it will not
628
		 *    show the opt-in right away. The reason it will happen is because Freemius execution
629
		 *    will be turned off till the plugin is fully loaded at least once
630
		 *    (till $this->_storage->was_plugin_loaded is TRUE).
631
		 *
632
		 * @author Vova Feldman (@svovaf)
633
		 * @since  1.1.9
634
		 *
635
		 */
636
		function _plugins_loaded() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
637
			// Update flag that plugin was loaded with Freemius at least once.
638
			$this->_storage->was_plugin_loaded = true;
639
640
			/**
641
			 * Bug fix - only set to false when it's a plugin, due to the
642
			 * execution sequence of the theme hooks and our methods, if
643
			 * this will be set for themes, Freemius will always assume
644
			 * it's a theme update.
645
			 *
646
			 * @author Vova Feldman (@svovaf)
647
			 * @since  1.2.2.2
648
			 */
649
			if ( $this->is_plugin() &&
650
			     ! isset( $this->_storage->is_plugin_new_install )
651
			) {
652
				$this->_storage->is_plugin_new_install = false;
653
			}
654
		}
655
656
		/**
657
		 * @author Vova Feldman (@svovaf)
658
		 * @since  1.0.9
659
		 */
660
		private function _register_hooks() {
661
			$this->_logger->entrance();
662
663
			if ( is_admin() ) {
664
				if ( $this->is_plugin() ) {
665
					$plugin_dir = dirname( $this->_plugin_dir_path ) . '/';
666
667
					/**
668
					 * @since 1.2.2
669
					 *
670
					 * Hook to both free and premium version activations to support
671
					 * auto deactivation on the other version activation.
672
					 */
673
					register_activation_hook(
674
						$plugin_dir . $this->_free_plugin_basename,
675
						array( &$this, '_activate_plugin_event_hook' )
676
					);
677
678
					register_activation_hook(
679
						$plugin_dir . $this->premium_plugin_basename(),
680
						array( &$this, '_activate_plugin_event_hook' )
681
					);
682
				} else {
683
					add_action( 'after_switch_theme', array( &$this, '_activate_theme_event_hook' ), 10, 2 );
684
685
					/**
686
					 * Include the required hooks to capture the theme settings' page tabs
687
					 * and cache them.
688
					 *
689
					 * @author Vova Feldman (@svovaf)
690
					 * @since 1.2.2.7
691
					 */
692
					if ( ! $this->_cache->has_valid( 'tabs' ) ) {
693
						add_action( 'admin_footer', array( &$this, '_tabs_capture' ) );
694
						// Add license activation AJAX callback.
695
						$this->add_ajax_action( 'store_tabs', array( &$this, '_store_tabs_ajax_action' ) );
696
697
						add_action( 'admin_enqueue_scripts', array( &$this, '_store_tabs_styles' ), 9999999 );
698
					}
699
700
					add_action(
701
						'admin_footer',
702
						array( &$this, '_add_freemius_tabs' ),
703
						/**
704
						 * The tabs JS code must be executed after the tabs capture logic (_tabs_capture()).
705
						 * That's why the priority is 11 while the tabs capture logic is added
706
						 * with priority 10.
707
						 *
708
						 * @author Vova Feldman (@svovaf)
709
						 */
710
						11
711
					);
712
713
					add_action( 'admin_footer', array( &$this, '_style_premium_theme' ) );
714
				}
715
716
				/**
717
				 * Part of the mechanism to identify new plugin install vs. plugin update.
718
				 *
719
				 * @author Vova Feldman (@svovaf)
720
				 * @since  1.1.9
721
				 */
722
				if ( empty( $this->_storage->was_plugin_loaded ) ) {
723
					if ( $this->is_plugin() &&
724
					     $this->is_activation_mode( false ) &&
725
					     0 == did_action( 'plugins_loaded' )
726
					) {
727
						add_action( 'plugins_loaded', array( &$this, '_plugins_loaded' ) );
728
					} else {
729
						// If was activated before, then it was already loaded before.
730
						$this->_plugins_loaded();
731
					}
732
				}
733
734
				if ( ! self::is_ajax() ) {
735
					if ( ! $this->is_addon() ) {
736
						add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY );
737
					}
738
				}
739
			}
740
741
			if ( $this->is_plugin() ) {
742
				register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) );
743
			}
744
745
			if ( $this->is_theme() && self::is_customizer() ) {
746
				// Register customizer upsell.
747
				add_action( 'customize_register', array( &$this, '_customizer_register' ) );
748
			}
749
750
			add_action( 'init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY );
751
752
			add_action( 'admin_init', array( &$this, '_add_tracking_links' ) );
753
			add_action( 'admin_init', array( &$this, '_add_license_activation' ) );
754
			$this->add_ajax_action( 'update_billing', array( &$this, '_update_billing_ajax_action' ) );
755
			$this->add_ajax_action( 'start_trial', array( &$this, '_start_trial_ajax_action' ) );
756
757
			$this->add_ajax_action( 'install_premium_version', array(
758
				&$this,
759
				'_install_premium_version_ajax_action'
760
			) );
761
762
            $this->add_ajax_action( 'submit_affiliate_application', array( &$this, '_submit_affiliate_application' ) );
763
764
			$this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) );
765
766
			$this->add_action( 'sdk_version_update', array( &$this, '_data_migration' ), WP_FS__DEFAULT_PRIORITY, 2 );
767
			$this->add_action( 'plugin_version_update', array( &$this, '_after_version_update' ), WP_FS__DEFAULT_PRIORITY, 2 );
768
			$this->add_filter( 'after_code_type_change', array( &$this, '_after_code_type_change' ) );
769
770
			add_action( 'admin_init', array( &$this, '_add_trial_notice' ) );
771
			add_action( 'admin_init', array( &$this, '_add_affiliate_program_notice' ) );
772
			add_action( 'admin_init', array( &$this, '_enqueue_common_css' ) );
773
774
			/**
775
			 * Handle request to reset anonymous mode for `get_reconnect_url()`.
776
			 *
777
			 * @author Vova Feldman (@svovaf)
778
			 * @since  1.2.1.5
779
			 */
780
			if ( fs_request_is_action( 'reset_anonymous_mode' ) &&
781
			     $this->get_unique_affix() === fs_request_get( 'fs_unique_affix' )
782
			) {
783
				add_action( 'admin_init', array( &$this, 'connect_again' ) );
784
			}
785
		}
786
787
		/**
788
		 * Keeping the uninstall hook registered for free or premium plugin version may result to a fatal error that
789
		 * could happen when a user tries to uninstall either version while one of them is still active. Uninstalling a
790
		 * plugin will trigger inclusion of the free or premium version and if one of them is active during the
791
		 * uninstallation, a fatal error may occur in case the plugin's class or functions are already defined.
792
		 *
793
		 * @author Leo Fajardo (leorw)
794
		 *
795
		 * @since  1.2.0
796
		 */
797
		private function unregister_uninstall_hook() {
798
			$uninstallable_plugins = (array) get_option( 'uninstall_plugins' );
799
			unset( $uninstallable_plugins[ $this->_free_plugin_basename ] );
800
			unset( $uninstallable_plugins[ $this->premium_plugin_basename() ] );
801
802
			update_option( 'uninstall_plugins', $uninstallable_plugins );
803
		}
804
805
		/**
806
		 * @since 1.2.0 Invalidate module's main file cache, otherwise, FS_Plugin_Updater will not fetch updates.
807
		 */
808
		private function clear_module_main_file_cache() {
809
			if ( ! isset( $this->_storage->plugin_main_file ) ||
810
			     empty( $this->_storage->plugin_main_file->path )
811
			) {
812
				return;
813
			}
814
815
			$plugin_main_file = clone $this->_storage->plugin_main_file;
816
817
			// Store cached path (2nd layer cache).
818
			$plugin_main_file->prev_path = $plugin_main_file->path;
819
820
			// Clear cached path.
821
			unset( $plugin_main_file->path );
822
823
			$this->_storage->plugin_main_file = $plugin_main_file;
824
825
			/**
826
			 * Clear global cached path.
827
			 *
828
			 * @author Leo Fajardo (@leorw)
829
			 * @since  1.2.2
830
			 */
831
			$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map' );
832
			unset( $id_slug_type_path_map[ $this->_module_id ]['path'] );
833
			self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true );
834
		}
835
836
		/**
837
		 * @author Vova Feldman (@svovaf)
838
		 * @since  1.0.9
839
		 */
840
		private function _register_account_hooks() {
841
			if ( ! is_admin() ) {
842
				return;
843
			}
844
845
			/**
846
			 * Always show the deactivation feedback form since we added
847
			 * automatic free version deactivation upon premium code activation.
848
			 *
849
			 * @since 1.2.1.6
850
			 */
851
			$this->add_ajax_action(
852
				'submit_uninstall_reason',
853
				array( &$this, '_submit_uninstall_reason_action' )
854
			);
855
856
			if ( ( $this->is_plugin() && self::is_plugins_page() ) ||
857
			     ( $this->is_theme() && self::is_themes_page() )
858
			) {
859
				add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) );
860
			}
861
		}
862
863
		/**
864
		 * Leverage backtrace to find caller plugin file path.
865
		 *
866
		 * @author Vova Feldman (@svovaf)
867
		 * @since  1.0.6
868
		 *
869
		 * @param  bool $is_init Is initiation sequence.
870
		 *
871
		 * @return string
872
		 */
873
		private function _find_caller_plugin_file( $is_init = false ) {
874
			// Try to load the cached value of the file path.
875
			if ( isset( $this->_storage->plugin_main_file ) ) {
876
				$plugin_main_file = $this->_storage->plugin_main_file;
877
				if ( isset( $plugin_main_file->path ) && file_exists( $plugin_main_file->path ) ) {
878
					return $plugin_main_file->path;
879
				}
880
			}
881
882
			/**
883
			 * @since 1.2.1
884
			 *
885
			 * `clear_module_main_file_cache()` is clearing the plugin's cached path on
886
			 * deactivation. Therefore, if any plugin/theme was initiating `Freemius`
887
			 * with that plugin's slug, it was overriding the empty plugin path with a wrong path.
888
			 *
889
			 * So, we've added a special mechanism with a 2nd layer of cache that uses `prev_path`
890
			 * when the class instantiator isn't the module.
891
			 */
892
			if ( ! $is_init ) {
893
				// Fetch prev path cache.
894
				if ( isset( $this->_storage->plugin_main_file ) &&
895
				     isset( $this->_storage->plugin_main_file->prev_path )
896
				) {
897
					if ( file_exists( $this->_storage->plugin_main_file->prev_path ) ) {
898
						return $this->_storage->plugin_main_file->prev_path;
899
					}
900
				}
901
902
				wp_die(
903
					$this->get_text( 'failed-finding-main-path' ) .
904
					" Module: {$this->_slug}; SDK: " . WP_FS__SDK_VERSION . ";",
905
					$this->get_text( 'error' ),
906
					array( 'back_link' => true )
907
				);
908
			}
909
910
			/**
911
			 * @since 1.2.1
912
			 *
913
			 * Only the original instantiator that calls dynamic_init can modify the module's path.
914
			 */
915
			// Find caller module.
916
			$id_slug_type_path_map            = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
917
			$this->_storage->plugin_main_file = (object) array(
918
				'path' => $id_slug_type_path_map[ $this->_module_id ]['path'],
919
			);
920
921
			return $id_slug_type_path_map[ $this->_module_id ]['path'];
922
		}
923
924
		/**
925
		 * @author Leo Fajardo (@leorw)
926
		 *
927
		 * @param number $module_id
928
		 * @param string $slug
929
		 *
930
		 * @since  1.2.2
931
		 */
932
		private function store_id_slug_type_path_map( $module_id, $slug ) {
933
			$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
934
935
			$store_option = false;
936
937
			if ( ! isset( $id_slug_type_path_map[ $module_id ] ) ) {
938
				$id_slug_type_path_map[ $module_id ] = array(
939
					'slug' => $slug
940
				);
941
942
				$store_option = true;
943
			}
944
945
			if ( ! isset( $id_slug_type_path_map[ $module_id ]['path'] ) ||
946
			     /**
947
			      * This verification is for cases when suddenly the same module
948
			      * is installed but with a different folder name.
949
			      *
950
			      * @author Vova Feldman (@svovaf)
951
			      * @since 1.2.3
952
			      */
953
			     ! file_exists( $id_slug_type_path_map[ $module_id ]['path'] )
954
			) {
955
				$caller_main_file_and_type = $this->get_caller_main_file_and_type();
956
957
				$id_slug_type_path_map[ $module_id ]['type'] = $caller_main_file_and_type->module_type;
958
				$id_slug_type_path_map[ $module_id ]['path'] = $caller_main_file_and_type->path;
959
960
				$store_option = true;
961
			}
962
963
			if ( $store_option ) {
964
				self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true );
965
			}
966
		}
967
968
		/**
969
		 * Identifies the caller type: plugin or theme.
970
		 *
971
		 * @author Leo Fajardo (@leorw)
972
		 * @since  1.2.2
973
		 *
974
		 * @author Vova Feldman (@svovaf)
975
		 * @since  1.2.2.3 Find the earliest module in the call stack that calls to the SDK. This fix is for cases when
976
		 *         add-ons are relying on loading the SDK from the parent module, and also allows themes including the
977
		 *         SDK an internal file instead of directly from functions.php.
978
		 * @since  1.2.1.7 Knows how to handle cases when an add-on includes the parent module logic.
979
		 */
980
		private function get_caller_main_file_and_type() {
981
			self::require_plugin_essentials();
982
983
			$all_plugins       = get_plugins();
984
			$all_plugins_paths = array();
985
986
			// Get active plugin's main files real full names (might be symlinks).
987
			foreach ( $all_plugins as $relative_path => &$data ) {
988
				if ( false === strpos( fs_normalize_path( $relative_path ), '/' ) ) {
989
					/**
990
					 * Ignore plugins that don't have a folder (e.g. Hello Dolly) since they
991
					 * can't really include the SDK.
992
					 *
993
					 * @author Vova Feldman
994
					 * @since  1.2.1.7
995
					 */
996
					continue;
997
				}
998
999
				$all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) );
1000
			}
1001
1002
			$caller_file_candidate = false;
1003
			$caller_map            = array();
1004
			$module_type           = WP_FS__MODULE_TYPE_PLUGIN;
1005
			$themes_dir            = fs_normalize_path( get_theme_root() );
1006
1007
			for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) {
1008
				if ( empty( $bt[ $i ]['file'] ) ) {
1009
					continue;
1010
				}
1011
1012
				if ( $i > 1 && ! empty( $bt[ $i - 1 ]['file'] ) && $bt[ $i ]['file'] === $bt[ $i - 1 ]['file'] ) {
1013
					// If file same as the prev file in the stack, skip it.
1014
					continue;
1015
				}
1016
1017
				if ( ! empty( $bt[ $i ]['function'] ) && in_array( $bt[ $i ]['function'], array(
1018
						'do_action',
1019
						'apply_filter',
1020
						// The string split is stupid, but otherwise, theme check
1021
						// throws info notices.
1022
						'requir' . 'e_once',
1023
						'requir' . 'e',
1024
						'includ' . 'e_once',
1025
						'includ' . 'e'
1026
					) )
1027
				) {
1028
					// Ignore call stack hooks and files inclusion.
1029
					continue;
1030
				}
1031
1032
				$caller_file_path = fs_normalize_path( $bt[ $i ]['file'] );
1033
1034
				if ( 'functions.php' === basename( $caller_file_path ) ) {
1035
					/**
1036
					 * 1. Assumes that theme's starting execution file is functions.php.
1037
					 * 2. This complex logic fixes symlink issues (e.g. with Vargant).
1038
					 *
1039
					 * @author Vova Feldman (@svovaf)
1040
					 * @since  1.2.2.5
1041
					 */
1042
1043
					if ( $caller_file_path == fs_normalize_path( realpath( trailingslashit( $themes_dir ) . basename( dirname( $caller_file_path ) ) . '/' . basename( $caller_file_path ) ) ) ) {
1044
						$module_type           = WP_FS__MODULE_TYPE_THEME;
1045
						$caller_file_candidate = $caller_file_path;
1046
						continue;
1047
					}
1048
				}
1049
1050
				$caller_file_hash = md5( $caller_file_path );
1051
1052
				if ( ! isset( $caller_map[ $caller_file_hash ] ) ) {
1053
					foreach ( $all_plugins_paths as $plugin_path ) {
1054
						if ( false !== strpos( $caller_file_path, fs_normalize_path( dirname( $plugin_path ) . '/' ) ) ) {
1055
							$caller_map[ $caller_file_hash ] = fs_normalize_path( $plugin_path );
1056
							break;
1057
						}
1058
					}
1059
				}
1060
1061
				if ( isset( $caller_map[ $caller_file_hash ] ) ) {
1062
					$module_type           = WP_FS__MODULE_TYPE_PLUGIN;
1063
					$caller_file_candidate = $caller_map[ $caller_file_hash ];
1064
				}
1065
			}
1066
1067
			return (object) array(
1068
				'module_type' => $module_type,
1069
				'path'        => $caller_file_candidate
1070
			);
1071
		}
1072
1073
		#----------------------------------------------------------------------------------
1074
		#region Deactivation Feedback Form
1075
		#----------------------------------------------------------------------------------
1076
1077
		/**
1078
		 * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins
1079
		 * page.
1080
		 *
1081
		 * @author Vova Feldman (@svovaf)
1082
		 * @author Leo Fajardo (@leorw)
1083
		 * @since  1.1.2
1084
		 */
1085
		function _add_deactivation_feedback_dialog_box() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1086
			/* Check the type of user:
1087
			 * 1. Long-term (long-term)
1088
			 * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term).
1089
			 * 3. Short-term (short-term)
1090
			 */
1091
			$is_long_term_user = true;
1092
1093
			// Check if the site is at least 2 days old.
1094
			$time_installed = $this->_storage->install_timestamp;
1095
1096
			// Difference in seconds.
1097
			$date_diff = time() - $time_installed;
1098
1099
			// Convert seconds to days.
1100
			$date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) );
1101
1102
			if ( $date_diff_days < 2 ) {
1103
				$is_long_term_user = false;
1104
			}
1105
1106
			$is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user );
1107
1108
			if ( $is_long_term_user ) {
1109
				$user_type = 'long-term';
1110
			} else {
1111
				if ( ! $this->is_registered() && ! $this->is_anonymous() ) {
1112
					$user_type = 'non-registered-and-non-anonymous-short-term';
1113
				} else {
1114
					$user_type = 'short-term';
1115
				}
1116
			}
1117
1118
			$uninstall_reasons = $this->_get_uninstall_reasons( $user_type );
1119
1120
			// Load the HTML template for the deactivation feedback dialog box.
1121
			$vars = array(
1122
				'reasons' => $uninstall_reasons,
1123
				'id'      => $this->_module_id
1124
			);
1125
1126
			/**
1127
			 * @todo Deactivation form core functions should be loaded only once! Otherwise, when there are multiple Freemius powered plugins the same code is loaded multiple times. The only thing that should be loaded differently is the various deactivation reasons object based on the state of the plugin.
1128
			 */
1129
			fs_require_template( 'forms/deactivation/form.php', $vars );
1130
		}
1131
1132
		/**
1133
		 * @author Leo Fajardo (leorw)
1134
		 * @since  1.1.2
1135
		 *
1136
		 * @param string $user_type
1137
		 *
1138
		 * @return array The uninstall reasons for the specified user type.
1139
		 */
1140
		function _get_uninstall_reasons( $user_type = 'long-term' ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1141
			$module_type = $this->_module_type;
1142
1143
			$internal_message_template_var = array(
1144
				'id' => $this->_module_id
1145
			);
1146
1147
			if ( $this->is_registered() && false !== $this->get_plan() && $this->get_plan()->has_technical_support() ) {
1148
				$contact_support_template = fs_get_template( 'forms/deactivation/contact.php', $internal_message_template_var );
1149
			} else {
1150
				$contact_support_template = '';
1151
			}
1152
1153
			$reason_found_better_plugin = array(
1154
				'id'                => self::REASON_FOUND_A_BETTER_PLUGIN,
1155
				'text'              => sprintf( $this->get_text( 'reason-found-a-better-plugin' ), $module_type ),
1156
				'input_type'        => 'textfield',
1157
				'input_placeholder' => sprintf( $this->get_text( 'placeholder-plugin-name' ), $module_type ),
1158
			);
1159
1160
			$reason_temporary_deactivation = array(
1161
				'id'                => self::REASON_TEMPORARY_DEACTIVATION,
1162
				'text'              => sprintf(
1163
					$this->get_text( 'reason-temporary-x' ),
1164
					strtolower( $this->is_plugin() ?
1165
						$this->get_text( 'deactivation' ) :
1166
						$this->get_text( 'theme-switch' )
1167
					)
1168
				),
1169
				'input_type'        => '',
1170
				'input_placeholder' => ''
1171
			);
1172
1173
			$reason_other = array(
1174
				'id'                => self::REASON_OTHER,
1175
				'text'              => $this->get_text( 'reason-other' ),
1176
				'input_type'        => 'textfield',
1177
				'input_placeholder' => ''
1178
			);
1179
1180
			$long_term_user_reasons = array(
1181
				array(
1182
					'id'                => self::REASON_NO_LONGER_NEEDED,
1183
					'text'              => sprintf( $this->get_text( 'reason-no-longer-needed' ), $module_type ),
1184
					'input_type'        => '',
1185
					'input_placeholder' => ''
1186
				),
1187
				$reason_found_better_plugin,
1188
				array(
1189
					'id'                => self::REASON_NEEDED_FOR_A_SHORT_PERIOD,
1190
					'text'              => sprintf( $this->get_text( 'reason-needed-for-a-short-period' ), $module_type ),
1191
					'input_type'        => '',
1192
					'input_placeholder' => ''
1193
				),
1194
				array(
1195
					'id'                => self::REASON_BROKE_MY_SITE,
1196
					'text'              => sprintf( $this->get_text( 'reason-broke-my-site' ), $module_type ),
1197
					'input_type'        => '',
1198
					'input_placeholder' => '',
1199
					'internal_message'  => $contact_support_template
1200
				),
1201
				array(
1202
					'id'                => self::REASON_SUDDENLY_STOPPED_WORKING,
1203
					'text'              => sprintf( $this->get_text( 'reason-suddenly-stopped-working' ), $module_type ),
1204
					'input_type'        => '',
1205
					'input_placeholder' => '',
1206
					'internal_message'  => $contact_support_template
1207
				)
1208
			);
1209
1210
			if ( $this->is_paying() ) {
1211
				$long_term_user_reasons[] = array(
1212
					'id'                => self::REASON_CANT_PAY_ANYMORE,
1213
					'text'              => $this->get_text( 'reason-cant-pay-anymore' ),
1214
					'input_type'        => 'textfield',
1215
					'input_placeholder' => $this->get_text( 'placeholder-comfortable-price' )
1216
				);
1217
			}
1218
1219
			$reason_dont_share_info = array(
1220
				'id'                => self::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION,
1221
				'text'              => $this->get_text( 'reason-dont-like-to-share-my-information' ),
1222
				'input_type'        => '',
1223
				'input_placeholder' => ''
1224
			);
1225
1226
			/**
1227
			 * If the current user has selected the "don't share data" reason in the deactivation feedback modal, inform the
1228
			 * user by showing additional message that he doesn't have to share data and can just choose to skip the opt-in
1229
			 * (the Skip button is included in the message to show). This message will only be shown if anonymous mode is
1230
			 * enabled and the user's account is currently not in pending activation state (similar to the way the Skip
1231
			 * button in the opt-in form is shown/hidden).
1232
			 */
1233
			if ( $this->is_enable_anonymous() && ! $this->is_pending_activation() ) {
1234
				$reason_dont_share_info['internal_message'] = fs_get_template( 'forms/deactivation/retry-skip.php', $internal_message_template_var );
1235
			}
1236
1237
			$uninstall_reasons = array(
1238
				'long-term'                                   => $long_term_user_reasons,
1239
				'non-registered-and-non-anonymous-short-term' => array(
1240
					array(
1241
						'id'                => self::REASON_DIDNT_WORK,
1242
						'text'              => sprintf( $this->get_text( 'reason-didnt-work' ), $module_type ),
1243
						'input_type'        => '',
1244
						'input_placeholder' => ''
1245
					),
1246
					$reason_dont_share_info,
1247
					$reason_found_better_plugin
1248
				),
1249
				'short-term'                                  => array(
1250
					array(
1251
						'id'                => self::REASON_COULDNT_MAKE_IT_WORK,
1252
						'text'              => $this->get_text( 'reason-couldnt-make-it-work' ),
1253
						'input_type'        => '',
1254
						'input_placeholder' => '',
1255
						'internal_message'  => $contact_support_template
1256
					),
1257
					$reason_found_better_plugin,
1258
					array(
1259
						'id'                => self::REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE,
1260
						'text'              => sprintf( $this->get_text( 'reason-great-but-need-specific-feature' ), $module_type ),
1261
						'input_type'        => 'textarea',
1262
						'input_placeholder' => $this->get_text( 'placeholder-feature' )
1263
					),
1264
					array(
1265
						'id'                => self::REASON_NOT_WORKING,
1266
						'text'              => sprintf( $this->get_text( 'reason-not-working' ), $module_type ),
1267
						'input_type'        => 'textarea',
1268
						'input_placeholder' => $this->get_text( 'placeholder-share-what-didnt-work' )
1269
					),
1270
					array(
1271
						'id'                => self::REASON_NOT_WHAT_I_WAS_LOOKING_FOR,
1272
						'text'              => $this->get_text( 'reason-not-what-i-was-looking-for' ),
1273
						'input_type'        => 'textarea',
1274
						'input_placeholder' => $this->get_text( 'placeholder-what-youve-been-looking-for' )
1275
					),
1276
					array(
1277
						'id'                => self::REASON_DIDNT_WORK_AS_EXPECTED,
1278
						'text'              => sprintf( $this->get_text( 'reason-didnt-work-as-expected' ), $module_type ),
1279
						'input_type'        => 'textarea',
1280
						'input_placeholder' => $this->get_text( 'placeholder-what-did-you-expect' )
1281
					)
1282
				)
1283
			);
1284
1285
			// Randomize the reasons for the current user type.
1286
			shuffle( $uninstall_reasons[ $user_type ] );
1287
1288
			// Keep the following reasons as the last items in the list.
1289
			$uninstall_reasons[ $user_type ][] = $reason_temporary_deactivation;
1290
			$uninstall_reasons[ $user_type ][] = $reason_other;
1291
1292
			$uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons );
1293
1294
			return $uninstall_reasons[ $user_type ];
1295
		}
1296
1297
		/**
1298
		 * Called after the user has submitted his reason for deactivating the plugin.
1299
		 *
1300
		 * @author Leo Fajardo (@leorw)
1301
		 * @since  1.1.2
1302
		 */
1303
		function _submit_uninstall_reason_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1304
			$this->_logger->entrance();
1305
1306
			$this->check_ajax_referer( 'submit_uninstall_reason' );
1307
1308
			$reason_id = fs_request_get( 'reason_id' );
1309
1310
			// Check if the given reason ID is an unsigned integer.
1311
			if ( ! ctype_digit( $reason_id ) ) {
1312
				exit;
1313
			}
1314
1315
			$reason_info = trim( fs_request_get( 'reason_info', '' ) );
1316
			if ( ! empty( $reason_info ) ) {
1317
				$reason_info = substr( $reason_info, 0, 128 );
1318
			}
1319
1320
			$reason = (object) array(
1321
				'id'           => $reason_id,
1322
				'info'         => $reason_info,
1323
				'is_anonymous' => fs_request_get_bool( 'is_anonymous' )
1324
			);
1325
1326
			$this->_storage->store( 'uninstall_reason', $reason );
1327
1328
			/**
1329
			 * If the module type is "theme", trigger the uninstall event here (on theme deactivation) since themes do
1330
			 * not support uninstall hook.
1331
			 *
1332
			 * @author Leo Fajardo (@leorw)
1333
			 * @since  1.2.2
1334
			 */
1335
			if ( $this->is_theme() ) {
1336
				$this->_uninstall_plugin_event( false );
1337
				$this->remove_sdk_reference();
1338
			}
1339
1340
			// Print '1' for successful operation.
1341
			echo 1;
1342
			exit;
1343
		}
1344
1345
		#endregion
1346
1347
		#----------------------------------------------------------------------------------
1348
		#region Instance
1349
		#----------------------------------------------------------------------------------
1350
1351
		/**
1352
		 * Main singleton instance.
1353
		 *
1354
		 * @author Vova Feldman (@svovaf)
1355
		 * @since  1.0.0
1356
		 *
1357
		 * @param  number      $module_id
1358
		 * @param  string|bool $slug
1359
		 * @param  bool        $is_init Is initiation sequence.
1360
		 *
1361
		 * @return Freemius|false
1362
		 */
1363
		static function instance( $module_id, $slug = false, $is_init = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1364
			if ( empty( $module_id ) ) {
1365
				return false;
1366
			}
1367
1368
			if ( ! is_numeric( $module_id ) ) {
1369
				if ( ! $is_init && true === $slug ) {
1370
					$is_init = true;
1371
				}
1372
1373
				$slug = $module_id;
1374
1375
				$module = FS_Plugin_Manager::instance( $slug )->get();
1376
1377
				if ( is_object( $module ) ) {
1378
					$module_id = $module->id;
1379
				}
1380
			}
1381
1382
			$key = 'm_' . $module_id;
1383
1384
			if ( ! isset( self::$_instances[ $key ] ) ) {
1385
				if ( 0 === count( self::$_instances ) ) {
1386
					self::_load_required_static();
1387
				}
1388
1389
				self::$_instances[ $key ] = new Freemius( $module_id, $slug, $is_init );
1390
			}
1391
1392
			return self::$_instances[ $key ];
1393
		}
1394
1395
		/**
1396
		 * @author Vova Feldman (@svovaf)
1397
		 * @since  1.0.6
1398
		 *
1399
		 * @param number $addon_id
1400
		 *
1401
		 * @return bool
1402
		 */
1403
		private static function has_instance( $addon_id ) {
1404
			return isset( self::$_instances[ 'm_' . $addon_id ] );
1405
		}
1406
1407
		/**
1408
		 * @author Leo Fajardo (@leorw)
1409
		 * @since  1.2.2
1410
		 *
1411
		 * @param  string|number $id_or_slug
1412
		 *
1413
		 * @return number|false
1414
		 */
1415
		private static function get_module_id( $id_or_slug ) {
1416
			if ( is_numeric( $id_or_slug ) ) {
1417
				return $id_or_slug;
1418
			}
1419
1420
			foreach ( self::$_instances as $instance ) {
1421
				if ( $instance->is_plugin() && ( $id_or_slug === $instance->get_slug() ) ) {
1422
					return $instance->get_id();
1423
				}
1424
			}
1425
1426
			return false;
1427
		}
1428
1429
		/**
1430
		 * @author Vova Feldman (@svovaf)
1431
		 * @since  1.0.6
1432
		 *
1433
		 * @param number $id
1434
		 *
1435
		 * @return false|Freemius
1436
		 */
1437
		static function get_instance_by_id( $id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1438
			return isset ( self::$_instances[ 'm_' . $id ] ) ?
1439
				self::$_instances[ 'm_' . $id ] :
1440
				false;
1441
		}
1442
1443
		/**
1444
		 *
1445
		 * @author Vova Feldman (@svovaf)
1446
		 * @since  1.0.1
1447
		 *
1448
		 * @param $plugin_file
1449
		 *
1450
		 * @return false|Freemius
1451
		 */
1452
		static function get_instance_by_file( $plugin_file ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1453
			$slug = self::find_slug_by_basename( $plugin_file );
1454
1455
			return ( false !== $slug ) ?
1456
				self::instance( self::get_module_id( $slug ) ) :
1457
				false;
1458
		}
1459
1460
		/**
1461
		 * @author Vova Feldman (@svovaf)
1462
		 * @since  1.0.6
1463
		 *
1464
		 * @return false|Freemius
1465
		 */
1466
		function get_parent_instance() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1467
			return self::get_instance_by_id( $this->_plugin->parent_plugin_id );
1468
		}
1469
1470
		/**
1471
		 * @author Vova Feldman (@svovaf)
1472
		 * @since  1.0.6
1473
		 *
1474
		 * @param  string|number $id_or_slug
1475
		 *
1476
		 * @return false|Freemius
1477
		 */
1478
		function get_addon_instance( $id_or_slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1479
			$addon_id = self::get_module_id( $id_or_slug );
1480
1481
			return self::instance( $addon_id );
1482
		}
1483
1484
		#endregion ------------------------------------------------------------------
1485
1486
		/**
1487
		 * @author Vova Feldman (@svovaf)
1488
		 * @since  1.0.6
1489
		 *
1490
		 * @return bool
1491
		 */
1492
		function is_parent_plugin_installed() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1493
			$is_active = self::has_instance( $this->_plugin->parent_plugin_id );
1494
1495
			if ( $is_active ) {
1496
				return true;
1497
			}
1498
1499
			/**
1500
			 * Parent module might be a theme. If that's the case, the add-on's FS
1501
			 * instance will be loaded prior to the theme's FS instance, therefore,
1502
			 * we need to check if it's active with a "look ahead".
1503
			 *
1504
			 * @author Vova Feldman
1505
			 * @since  1.2.2.3
1506
			 */
1507
			global $fs_active_plugins;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1508
			if ( is_object( $fs_active_plugins ) && is_array( $fs_active_plugins->plugins ) ) {
1509
				$active_theme = wp_get_theme();
1510
1511
				foreach ( $fs_active_plugins->plugins as $sdk => $module ) {
1512
					if ( WP_FS__MODULE_TYPE_THEME === $module->type ) {
1513
						if ( $module->plugin_path == $active_theme->get_stylesheet() ) {
1514
							// Parent module is a theme and it's currently active.
1515
							return true;
1516
						}
1517
					}
1518
				}
1519
			}
1520
1521
			return false;
1522
		}
1523
1524
		/**
1525
		 * Check if add-on parent plugin in activation mode.
1526
		 *
1527
		 * @author Vova Feldman (@svovaf)
1528
		 * @since  1.0.7
1529
		 *
1530
		 * @return bool
1531
		 */
1532
		function is_parent_in_activation() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1533
			$parent_fs = $this->get_parent_instance();
1534
			if ( ! is_object( $parent_fs ) ) {
1535
				return false;
1536
			}
1537
1538
			return ( $parent_fs->is_activation_mode() );
1539
		}
1540
1541
		/**
1542
		 * Is plugin in activation mode.
1543
		 *
1544
		 * @author Vova Feldman (@svovaf)
1545
		 * @since  1.0.7
1546
		 *
1547
		 * @param bool $and_on
1548
		 *
1549
		 * @return bool
1550
		 */
1551
		function is_activation_mode( $and_on = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1552
			return (
1553
				( $this->is_on() || ! $and_on ) &&
1554
				( ! $this->is_registered() || ( $this->is_only_premium() && ! $this->has_features_enabled_license() ) ) &&
1555
				( ! $this->is_enable_anonymous() ||
1556
				  ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) )
1557
			);
1558
		}
1559
1560
		/**
1561
		 * Check if current page is the opt-in/pending-activation page.
1562
		 *
1563
		 * @author Vova Feldman (@svovaf)
1564
		 * @since  1.2.1.7
1565
		 *
1566
		 * @return bool
1567
		 */
1568
		function is_activation_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1569
			if ( $this->_menu->is_main_settings_page() ) {
1570
				return true;
1571
			}
1572
1573
			if ( ! $this->is_activation_mode() ) {
1574
				return false;
1575
			}
1576
1577
			// Check if current page is matching the activation page.
1578
			return $this->is_matching_url( $this->get_activation_url() );
1579
		}
1580
1581
		/**
1582
		 * Check if URL path's are matching and that all querystring
1583
		 * arguments of the $sub_url exist in the $url with the same values.
1584
		 *
1585
		 * WARNING:
1586
		 *  1. This method doesn't check if the sub/domain are matching.
1587
		 *  2. Ignore case sensitivity.
1588
		 *
1589
		 * @author Vova Feldman (@svovaf)
1590
		 * @since  1.2.1.7
1591
		 *
1592
		 * @param string $sub_url
1593
		 * @param string $url     If argument is not set, check if the sub_url matching the current's page URL.
1594
		 *
1595
		 * @return bool
1596
		 */
1597
		private function is_matching_url( $sub_url, $url = '' ) {
0 ignored issues
show
Coding Style introduced by
is_matching_url uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1598
			if ( empty( $url ) ) {
1599
				$url = $_SERVER['REQUEST_URI'];
1600
			}
1601
1602
			$url     = strtolower( $url );
1603
			$sub_url = strtolower( $sub_url );
1604
1605
			if ( parse_url( $sub_url, PHP_URL_PATH ) !== parse_url( $url, PHP_URL_PATH ) ) {
1606
				// Different path - DO NOT OVERRIDE PAGE.
1607
				return false;
1608
			}
1609
1610
			$url_params = array();
1611
			parse_str( parse_url( $url, PHP_URL_QUERY ), $url_params );
1612
1613
			$sub_url_params = array();
1614
			parse_str( parse_url( $sub_url, PHP_URL_QUERY ), $sub_url_params );
1615
1616
			foreach ( $sub_url_params as $key => $val ) {
0 ignored issues
show
Bug introduced by
The expression $sub_url_params of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1617
				if ( ! isset( $url_params[ $key ] ) || $val != $url_params[ $key ] ) {
1618
					// Not matching query string - DO NOT OVERRIDE PAGE.
1619
					return false;
1620
				}
1621
			}
1622
1623
			return true;
1624
		}
1625
1626
		/**
1627
		 * Get collection of all active plugins.
1628
		 *
1629
		 * @author Vova Feldman (@svovaf)
1630
		 * @since  1.0.9
1631
		 *
1632
		 * @return array[string]array
0 ignored issues
show
Documentation introduced by
The doc-type array[string]array could not be parsed: Expected "]" at position 2, but found "string". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1633
		 */
1634
		private static function get_active_plugins() {
1635
			self::require_plugin_essentials();
1636
1637
			$active_plugin            = array();
1638
			$all_plugins              = get_plugins();
1639
			$active_plugins_basenames = get_option( 'active_plugins' );
1640
1641
			foreach ( $active_plugins_basenames as $plugin_basename ) {
1642
				$active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ];
1643
			}
1644
1645
			return $active_plugin;
1646
		}
1647
1648
		/**
1649
		 * Get collection of all plugins.
1650
		 *
1651
		 * @author Vova Feldman (@svovaf)
1652
		 * @since  1.1.8
1653
		 *
1654
		 * @return array Key is the plugin file path and the value is an array of the plugin data.
1655
		 */
1656
		private static function get_all_plugins() {
1657
			self::require_plugin_essentials();
1658
1659
			$all_plugins              = get_plugins();
1660
			$active_plugins_basenames = get_option( 'active_plugins' );
1661
1662
			foreach ( $all_plugins as $basename => &$data ) {
1663
				// By default set to inactive (next foreach update the active plugins).
1664
				$data['is_active'] = false;
1665
				// Enrich with plugin slug.
1666
				$data['slug'] = self::get_plugin_slug( $basename );
1667
			}
1668
1669
			// Flag active plugins.
1670
			foreach ( $active_plugins_basenames as $basename ) {
1671
				if ( isset( $all_plugins[ $basename ] ) ) {
1672
					$all_plugins[ $basename ]['is_active'] = true;
1673
				}
1674
			}
1675
1676
			return $all_plugins;
1677
		}
1678
1679
1680
		/**
1681
		 * Cached result of get_site_transient( 'update_plugins' )
1682
		 *
1683
		 * @author Vova Feldman (@svovaf)
1684
		 * @since  1.1.8
1685
		 *
1686
		 * @var object
1687
		 */
1688
		private static $_plugins_info;
1689
1690
		/**
1691
		 * Helper function to get specified plugin's slug.
1692
		 *
1693
		 * @author Vova Feldman (@svovaf)
1694
		 * @since  1.1.8
1695
		 *
1696
		 * @param $basename
1697
		 *
1698
		 * @return string
1699
		 */
1700
		private static function get_plugin_slug( $basename ) {
1701
			if ( ! isset( self::$_plugins_info ) ) {
1702
				self::$_plugins_info = get_site_transient( 'update_plugins' );
1703
			}
1704
1705
			$slug = '';
1706
1707
			if ( is_object( self::$_plugins_info ) ) {
1708
				if ( isset( self::$_plugins_info->no_update ) &&
1709
				     isset( self::$_plugins_info->no_update[ $basename ] ) &&
1710
				     ! empty( self::$_plugins_info->no_update[ $basename ]->slug )
1711
				) {
1712
					$slug = self::$_plugins_info->no_update[ $basename ]->slug;
1713
				} else if ( isset( self::$_plugins_info->response ) &&
1714
				            isset( self::$_plugins_info->response[ $basename ] ) &&
1715
				            ! empty( self::$_plugins_info->response[ $basename ]->slug )
1716
				) {
1717
					$slug = self::$_plugins_info->response[ $basename ]->slug;
1718
				}
1719
			}
1720
1721
			if ( empty( $slug ) ) {
1722
				// Try to find slug from FS data.
1723
				$slug = self::find_slug_by_basename( $basename );
1724
			}
1725
1726
			if ( empty( $slug ) ) {
1727
				// Fallback to plugin's folder name.
1728
				$slug = dirname( $basename );
1729
			}
1730
1731
			return $slug;
1732
		}
1733
1734
		private static $_statics_loaded = false;
1735
1736
		/**
1737
		 * Load static resources.
1738
		 *
1739
		 * @author Vova Feldman (@svovaf)
1740
		 * @since  1.0.1
1741
		 */
1742
		private static function _load_required_static() {
1743
			if ( self::$_statics_loaded ) {
1744
				return;
1745
			}
1746
1747
			self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
1748
1749
			self::$_static_logger->entrance();
1750
1751
			self::$_accounts = FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true );
1752
1753
			self::$_global_admin_notices = FS_Admin_Notice_Manager::instance( 'global' );
1754
1755
			add_action( 'admin_menu', array( 'Freemius', '_add_debug_section' ) );
1756
1757
			add_action( "wp_ajax_fs_toggle_debug_mode", array( 'Freemius', '_toggle_debug_mode' ) );
1758
1759
			self::add_ajax_action_static( 'get_debug_log', array( 'Freemius', '_get_debug_log' ) );
1760
1761
			self::add_ajax_action_static( 'get_db_option', array( 'Freemius', '_get_db_option' ) );
1762
1763
			self::add_ajax_action_static( 'set_db_option', array( 'Freemius', '_set_db_option' ) );
1764
1765
			if ( 0 == did_action( 'plugins_loaded' ) ) {
1766
				add_action( 'plugins_loaded', array( 'Freemius', '_load_textdomain' ), 1 );
1767
			}
1768
1769
			self::$_statics_loaded = true;
1770
		}
1771
1772
		#----------------------------------------------------------------------------------
1773
		#region Localization
1774
		#----------------------------------------------------------------------------------
1775
1776
		/**
1777
		 * Load framework's text domain.
1778
		 *
1779
		 * @author Vova Feldman (@svovaf)
1780
		 * @since  1.2.1
1781
		 */
1782
		static function _load_textdomain() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1783
			if ( ! is_admin() ) {
1784
				return;
1785
			}
1786
1787
			global $fs_active_plugins;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1788
1789
			// Works both for plugins and themes.
1790
			load_plugin_textdomain(
1791
				'freemius',
1792
				false,
1793
				$fs_active_plugins->newest->sdk_path . '/languages/'
1794
			);
1795
		}
1796
1797
		#endregion
1798
1799
		#----------------------------------------------------------------------------------
1800
		#region Debugging
1801
		#----------------------------------------------------------------------------------
1802
1803
		/**
1804
		 * @author Vova Feldman (@svovaf)
1805
		 * @since  1.0.8
1806
		 */
1807
		static function _add_debug_section() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1808
			if ( ! current_user_can( 'activate_plugins' )
1809
			     && ! current_user_can( 'switch_themes' )
1810
			) {
1811
				return;
1812
			}
1813
1814
			self::$_static_logger->entrance();
1815
1816
			$title = sprintf( '%s [v.%s]', fs_text( 'freemius-debug' ), WP_FS__SDK_VERSION );
1817
1818
			if ( WP_FS__DEV_MODE ) {
1819
				// Add top-level debug menu item.
1820
				$hook = FS_Admin_Menu_Manager::add_page(
1821
					$title,
1822
					$title,
1823
					'manage_options',
1824
					'freemius',
1825
					array( 'Freemius', '_debug_page_render' )
1826
				);
1827
			} else {
1828
				// Add hidden debug page.
1829
				$hook = FS_Admin_Menu_Manager::add_subpage(
1830
					null,
1831
					$title,
1832
					$title,
1833
					'manage_options',
1834
					'freemius',
1835
					array( 'Freemius', '_debug_page_render' )
1836
				);
1837
			}
1838
1839
			if ( ! empty( $hook ) ) {
1840
				add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) );
1841
			}
1842
		}
1843
1844
		/**
1845
		 * @author Vova Feldman (@svovaf)
1846
		 * @since  1.1.7.3
1847
		 */
1848
		static function _toggle_debug_mode() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1849
			$is_on = fs_request_get( 'is_on', false, 'post' );
1850
1851
			if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) {
1852
				update_option( 'fs_debug_mode', $is_on );
1853
1854
				// Turn on/off storage logging.
1855
				FS_Logger::_set_storage_logging( ( 1 == $is_on ) );
1856
			}
1857
1858
			exit;
1859
		}
1860
1861
		/**
1862
		 * @author Vova Feldman (@svovaf)
1863
		 * @since  1.2.1.6
1864
		 */
1865
		static function _get_debug_log() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
Coding Style introduced by
_get_debug_log uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1866
			$logs = FS_Logger::load_db_logs(
1867
				fs_request_get( 'filters', false, 'post' ),
1868
				! empty( $_POST['limit'] ) && is_numeric( $_POST['limit'] ) ? $_POST['limit'] : 200,
1869
				! empty( $_POST['offset'] ) && is_numeric( $_POST['offset'] ) ? $_POST['offset'] : 0
1870
			);
1871
1872
			self::shoot_ajax_success( $logs );
1873
		}
1874
1875
		/**
1876
		 * @author Vova Feldman (@svovaf)
1877
		 * @since  1.2.1.7
1878
		 */
1879
		static function _get_db_option() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1880
			$option_name = fs_request_get( 'option_name' );
1881
1882
			$value = get_option( $option_name );
1883
1884
			$result = array(
1885
				'name' => $option_name,
1886
			);
1887
1888
			if ( false !== $value ) {
1889
				if ( ! is_string( $value ) ) {
1890
					$value = json_encode( $value );
1891
				}
1892
1893
				$result['value'] = $value;
1894
			}
1895
1896
			self::shoot_ajax_success( $result );
1897
		}
1898
1899
		/**
1900
		 * @author Vova Feldman (@svovaf)
1901
		 * @since  1.2.1.7
1902
		 */
1903
		static function _set_db_option() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1904
			$option_name  = fs_request_get( 'option_name' );
1905
			$option_value = fs_request_get( 'option_value' );
1906
1907
			if ( ! empty( $option_value ) ) {
1908
				update_option( $option_name, $option_value );
1909
			}
1910
1911
			self::shoot_ajax_success();
1912
		}
1913
1914
		/**
1915
		 * @author Vova Feldman (@svovaf)
1916
		 * @since  1.0.8
1917
		 */
1918
		static function _debug_page_actions() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1919
			self::_clean_admin_content_section();
1920
1921
			if ( fs_request_is_action( 'restart_freemius' ) ) {
1922
				check_admin_referer( 'restart_freemius' );
1923
1924
				// Clear accounts data.
1925
				self::$_accounts->clear( true );
1926
1927
				// Clear SDK reference cache.
1928
				delete_option( 'fs_active_plugins' );
1929
			} else if ( fs_request_is_action( 'simulate_trial' ) ) {
1930
				check_admin_referer( 'simulate_trial' );
1931
1932
				$fs = freemius( fs_request_get( 'module_id' ) );
1933
1934
				// Update SDK install to at least 24 hours before.
1935
				$fs->_storage->install_timestamp = ( time() - WP_FS__TIME_24_HOURS_IN_SEC );
1936
				// Unset the trial shown timestamp.
1937
				unset( $fs->_storage->trial_promotion_shown );
1938
			} else if ( fs_request_is_action( 'delete_install' ) ) {
1939
				check_admin_referer( 'delete_install' );
1940
1941
				self::_delete_site_by_slug(
1942
					fs_request_get( 'slug' ),
1943
					fs_request_get( 'module_type' )
1944
				);
1945
			} else if ( fs_request_is_action( 'download_logs' ) ) {
1946
				check_admin_referer( 'download_logs' );
1947
1948
				$download_url = FS_Logger::download_db_logs(
1949
					fs_request_get( 'filters', false, 'post' )
1950
				);
1951
1952
				if ( false === $download_url ) {
1953
					wp_die( 'Oops... there was an error while generating the logs download file. Please try again and if it doesn\'t work contact [email protected].' );
1954
				}
1955
1956
				fs_redirect( $download_url );
0 ignored issues
show
Security Bug introduced by
It seems like $download_url defined by \FS_Logger::download_db_...lters', false, 'post')) on line 1948 can also be of type false; however, fs_redirect() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1957
			}
1958
		}
1959
1960
		/**
1961
		 * @author Vova Feldman (@svovaf)
1962
		 * @since  1.0.8
1963
		 */
1964
		static function _debug_page_render() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1965
			self::$_static_logger->entrance();
1966
1967
			$vars = array(
1968
				'plugin_sites'    => self::get_all_sites(),
1969
				'theme_sites'     => self::get_all_sites( WP_FS__MODULE_TYPE_THEME ),
1970
				'users'           => self::get_all_users(),
1971
				'addons'          => self::get_all_addons(),
1972
				'account_addons'  => self::get_all_account_addons(),
1973
				'plugin_licenses' => self::get_all_licenses(),
1974
				'theme_licenses'  => self::get_all_licenses( WP_FS__MODULE_TYPE_THEME )
1975
			);
1976
1977
			fs_enqueue_local_style( 'fs_debug', '/admin/debug.css' );
1978
			fs_require_once_template( 'debug.php', $vars );
1979
		}
1980
1981
		#endregion
1982
1983
		#----------------------------------------------------------------------------------
1984
		#region Connectivity Issues
1985
		#----------------------------------------------------------------------------------
1986
1987
		/**
1988
		 * Check if Freemius should be turned on for the current plugin install.
1989
		 *
1990
		 * Note:
1991
		 *  $this->_is_on is updated in has_api_connectivity()
1992
		 *
1993
		 * @author Vova Feldman (@svovaf)
1994
		 * @since  1.0.9
1995
		 *
1996
		 * @return bool
1997
		 */
1998
		function is_on() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1999
			self::$_static_logger->entrance();
2000
2001
			if ( isset( $this->_is_on ) ) {
2002
				return $this->_is_on;
2003
			}
2004
2005
			// If already installed or pending then sure it's on :)
2006
			if ( $this->is_registered() || $this->is_pending_activation() ) {
2007
				$this->_is_on = true;
2008
2009
				return true;
2010
			}
2011
2012
			return false;
2013
		}
2014
2015
		/**
2016
		 * @author Vova Feldman (@svovaf)
2017
		 * @since  1.1.7.3
2018
		 *
2019
		 * @param bool $flush_if_no_connectivity
2020
		 *
2021
		 * @return bool
2022
		 */
2023
		private function should_run_connectivity_test( $flush_if_no_connectivity = false ) {
0 ignored issues
show
Coding Style introduced by
should_run_connectivity_test uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2024
			if ( ! isset( $this->_storage->connectivity_test ) ) {
2025
				// Connectivity test was never executed, or cache was cleared.
2026
				return true;
2027
			}
2028
2029
			if ( WP_FS__PING_API_ON_IP_OR_HOST_CHANGES ) {
2030
				if ( WP_FS__IS_HTTP_REQUEST ) {
2031
					if ( $_SERVER['HTTP_HOST'] != $this->_storage->connectivity_test['host'] ) {
2032
						// Domain changed.
2033
						return true;
2034
					}
2035
2036
					if ( WP_FS__REMOTE_ADDR != $this->_storage->connectivity_test['server_ip'] ) {
2037
						// Server IP changed.
2038
						return true;
2039
					}
2040
				}
2041
			}
2042
2043
			if ( $this->_storage->connectivity_test['is_connected'] &&
2044
			     $this->_storage->connectivity_test['is_active']
2045
			) {
2046
				// API connected and Freemius is active - no need to run connectivity check.
2047
				return false;
2048
			}
2049
2050
			if ( $flush_if_no_connectivity ) {
2051
				/**
2052
				 * If explicitly asked to flush when no connectivity - do it only
2053
				 * if at least 10 sec passed from the last API connectivity test.
2054
				 */
2055
				return ( isset( $this->_storage->connectivity_test['timestamp'] ) &&
2056
				         ( WP_FS__SCRIPT_START_TIME - $this->_storage->connectivity_test['timestamp'] ) > 10 );
2057
			}
2058
2059
			/**
2060
			 * @since 1.1.7 Don't check for connectivity on plugin downgrade.
2061
			 */
2062
			$version = $this->get_plugin_version();
2063
			if ( version_compare( $version, $this->_storage->connectivity_test['version'], '>' ) ) {
2064
				// If it's a plugin version upgrade and Freemius is off or no connectivity, run connectivity test.
2065
				return true;
2066
			}
2067
2068
			return false;
2069
		}
2070
2071
		/**
2072
		 * @author Vova Feldman (@svovaf)
2073
		 * @since  1.1.7.4
2074
		 *
2075
		 * @return object|false
2076
		 */
2077
		private function ping() {
2078
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) {
2079
				return false;
2080
			}
2081
2082
			$version = $this->get_plugin_version();
2083
2084
			$is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() );
2085
2086
			return $this->get_api_plugin_scope()->ping(
2087
				$this->get_anonymous_id(),
2088
				array(
2089
					'is_update' => json_encode( $is_update ),
2090
					'version'   => $version,
2091
					'sdk'       => $this->version,
2092
					'is_admin'  => json_encode( is_admin() ),
2093
					'is_ajax'   => json_encode( self::is_ajax() ),
2094
					'is_cron'   => json_encode( self::is_cron() ),
2095
					'is_http'   => json_encode( WP_FS__IS_HTTP_REQUEST ),
2096
				)
2097
			);
2098
		}
2099
2100
		/**
2101
		 * Check if there's any connectivity issue to Freemius API.
2102
		 *
2103
		 * @author Vova Feldman (@svovaf)
2104
		 * @since  1.0.9
2105
		 *
2106
		 * @param bool $flush_if_no_connectivity
2107
		 *
2108
		 * @return bool
2109
		 */
2110
		function has_api_connectivity( $flush_if_no_connectivity = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2111
			$this->_logger->entrance();
2112
2113
			if ( isset( $this->_has_api_connection ) && ( $this->_has_api_connection || ! $flush_if_no_connectivity ) ) {
2114
				return $this->_has_api_connection;
2115
			}
2116
2117
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY &&
2118
			     isset( $this->_storage->connectivity_test ) &&
2119
			     true === $this->_storage->connectivity_test['is_connected']
2120
			) {
2121
				unset( $this->_storage->connectivity_test );
2122
			}
2123
2124
			if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) {
2125
				$this->_has_api_connection = $this->_storage->connectivity_test['is_connected'];
2126
				/**
2127
				 * @since 1.1.6 During dev mode, if there's connectivity - turn Freemius on regardless the configuration.
2128
				 *
2129
				 * @since 1.2.1.5 If the user running the premium version then ignore the 'is_active' flag and turn Freemius on to enable license key activation.
2130
				 */
2131
				$this->_is_on = $this->_storage->connectivity_test['is_active'] ||
2132
				                $this->is_premium() ||
2133
				                ( WP_FS__DEV_MODE && $this->_has_api_connection && ! WP_FS__SIMULATE_FREEMIUS_OFF );
2134
2135
				return $this->_has_api_connection;
2136
			}
2137
2138
			$pong         = $this->ping();
2139
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2140
2141
			if ( ! $is_connected ) {
2142
				// API failure.
2143
				$this->_add_connectivity_issue_message( $pong );
2144
			}
2145
2146
			$this->store_connectivity_info( $pong, $is_connected );
0 ignored issues
show
Security Bug introduced by
It seems like $pong defined by $this->ping() on line 2138 can also be of type false; however, Freemius::store_connectivity_info() does only seem to accept object, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2147
2148
			return $this->_has_api_connection;
2149
		}
2150
2151
		/**
2152
		 * @author Vova Feldman (@svovaf)
2153
		 * @since  1.1.7.4
2154
		 *
2155
		 * @param object $pong
2156
		 * @param bool   $is_connected
2157
		 */
2158
		private function store_connectivity_info( $pong, $is_connected ) {
0 ignored issues
show
Coding Style introduced by
store_connectivity_info uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2159
			$this->_logger->entrance();
2160
2161
			$version = $this->get_plugin_version();
2162
2163
			if ( ! $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) {
2164
				$is_active = false;
2165
			} else {
2166
				$is_active = ( isset( $pong->is_active ) && true == $pong->is_active );
2167
			}
2168
2169
			$is_active = $this->apply_filters(
2170
				'is_on',
2171
				$is_active,
2172
				$this->is_plugin_update(),
2173
				$version
2174
			);
2175
2176
			$this->_storage->connectivity_test = array(
2177
				'is_connected' => $is_connected,
2178
				'host'         => $_SERVER['HTTP_HOST'],
2179
				'server_ip'    => WP_FS__REMOTE_ADDR,
2180
				'is_active'    => $is_active,
2181
				'timestamp'    => WP_FS__SCRIPT_START_TIME,
2182
				// Last version with connectivity attempt.
2183
				'version'      => $version,
2184
			);
2185
2186
			$this->_has_api_connection = $is_connected;
2187
			$this->_is_on              = $is_active || ( WP_FS__DEV_MODE && $is_connected && ! WP_FS__SIMULATE_FREEMIUS_OFF );
2188
		}
2189
2190
		/**
2191
		 * Force turning Freemius on.
2192
		 *
2193
		 * @author Vova Feldman (@svovaf)
2194
		 * @since  1.1.8.1
2195
		 *
2196
		 * @return bool TRUE if successfully turned on.
2197
		 */
2198
		private function turn_on() {
2199
			$this->_logger->entrance();
2200
2201
			if ( $this->is_on() || ! isset( $this->_storage->connectivity_test['is_active'] ) ) {
2202
				return false;
2203
			}
2204
2205
			$updated_connectivity              = $this->_storage->connectivity_test;
2206
			$updated_connectivity['is_active'] = true;
2207
			$updated_connectivity['timestamp'] = WP_FS__SCRIPT_START_TIME;
2208
			$this->_storage->connectivity_test = $updated_connectivity;
2209
2210
			$this->_is_on = true;
2211
2212
			return true;
2213
		}
2214
2215
		/**
2216
		 * Anonymous and unique site identifier (Hash).
2217
		 *
2218
		 * @author Vova Feldman (@svovaf)
2219
		 * @since  1.1.0
2220
		 *
2221
		 * @return string
2222
		 */
2223
		function get_anonymous_id() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2224
			$unique_id = self::$_accounts->get_option( 'unique_id' );
2225
2226
			if ( empty( $unique_id ) || ! is_string( $unique_id ) ) {
2227
				$key = get_site_url();
2228
2229
				// If localhost, assign microtime instead of domain.
2230
				if ( WP_FS__IS_LOCALHOST ||
2231
				     false !== strpos( $key, 'localhost' ) ||
2232
				     false === strpos( $key, '.' )
2233
				) {
2234
					$key = microtime();
2235
				}
2236
2237
				$unique_id = md5( $key );
2238
2239
				self::$_accounts->set_option( 'unique_id', $unique_id, true );
2240
			}
2241
2242
			$this->_logger->departure( $unique_id );
2243
2244
			return $unique_id;
2245
		}
2246
2247
		/**
2248
		 * @author Vova Feldman (@svovaf)
2249
		 * @since  1.1.7.4
2250
		 *
2251
		 * @return \WP_User
2252
		 */
2253
		static function _get_current_wp_user() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2254
			self::require_pluggable_essentials();
2255
2256
			return wp_get_current_user();
2257
		}
2258
2259
		/**
2260
		 * @author Vova Feldman (@svovaf)
2261
		 * @since  1.2.1.7
2262
		 *
2263
		 * @param string $email
2264
		 *
2265
		 * @return bool
2266
		 */
2267
		static function is_valid_email( $email ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2268
			if ( false === filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
2269
				return false;
2270
			}
2271
2272
			$parts = explode( '@', $email );
2273
2274
			if ( 2 !== count( $parts ) || empty( $parts[1] ) ) {
2275
				return false;
2276
			}
2277
2278
			$blacklist = array(
2279
				'admin.',
2280
				'webmaster.',
2281
				'localhost.',
2282
				'dev.',
2283
				'development.',
2284
				'test.',
2285
				'stage.',
2286
				'staging.',
2287
			);
2288
2289
			// Make sure domain is not one of the blacklisted.
2290
			foreach ( $blacklist as $invalid ) {
2291
				if ( 0 === strpos( $parts[1], $invalid ) ) {
2292
					return false;
2293
				}
2294
			}
2295
2296
			// Get the UTF encoded domain name.
2297
			$domain = idn_to_ascii( $parts[1] ) . '.';
2298
2299
			return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) );
2300
		}
2301
		
2302
		/**
2303
		 * Generate API connectivity issue message.
2304
		 *
2305
		 * @author Vova Feldman (@svovaf)
2306
		 * @since  1.0.9
2307
		 *
2308
		 * @param mixed $api_result
2309
		 * @param bool  $is_first_failure
2310
		 */
2311
		function _add_connectivity_issue_message( $api_result, $is_first_failure = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2312
			if ( ! $this->is_premium() && $this->_enable_anonymous ) {
2313
				// Don't add message if it's the free version and can run anonymously.
2314
				return;
2315
			}
2316
2317
			if ( ! function_exists( 'wp_nonce_url' ) ) {
2318
				require_once ABSPATH . 'wp-includes/functions.php';
2319
			}
2320
2321
			$current_user = self::_get_current_wp_user();
2322
//			$admin_email = get_option( 'admin_email' );
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2323
			$admin_email = $current_user->user_email;
2324
2325
			$message = false;
2326
			if ( is_object( $api_result ) &&
2327
			     isset( $api_result->error ) &&
2328
			     isset( $api_result->error->code )
2329
			) {
2330
				switch ( $api_result->error->code ) {
2331
					case 'curl_missing':
2332
						$missing_methods = '';
2333
						if ( is_array( $api_result->missing_methods ) &&
2334
						     ! empty( $api_result->missing_methods )
2335
						) {
2336
							foreach ( $api_result->missing_methods as $m ) {
2337
								if ( 'curl_version' === $m ) {
2338
									continue;
2339
								}
2340
2341
								if ( ! empty( $missing_methods ) ) {
2342
									$missing_methods .= ', ';
2343
								}
2344
2345
								$missing_methods .= sprintf( '<code>%s</code>', $m );
2346
							}
2347
2348
							if ( ! empty( $missing_methods ) ) {
2349
								$missing_methods = sprintf(
2350
									'<br><br><b>%s</b> %s',
2351
									$this->get_text( 'curl-disabled-methods' ),
2352
									$missing_methods
2353
								);
2354
							}
2355
						}
2356
2357
						$message = sprintf(
2358
							$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2359
							$this->get_text( 'curl-missing-message' ) . ' ' .
2360
							$missing_methods .
2361
							' %s',
2362
							'<b>' . $this->get_plugin_name() . '</b>',
2363
							sprintf(
2364
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2365
								sprintf(
2366
									'<a class="fs-resolve" data-type="curl" href="#"><b>%s</b></a>%s',
2367
									$this->get_text( 'curl-missing-no-clue-title' ),
2368
									' - ' . sprintf(
2369
										$this->get_text( 'curl-missing-no-clue-desc' ),
2370
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2371
									)
2372
								),
2373
								sprintf(
2374
									'<b>%s</b> - %s',
2375
									$this->get_text( 'sysadmin-title' ),
2376
									sprintf( $this->get_text( 'curl-missing-sysadmin-desc' ), $this->_module_type )
2377
								),
2378
								sprintf(
2379
									'<a href="%s"><b>%s</b></a>%s',
2380
									wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . $this->_plugin_basename . '&amp;plugin_status=' . 'all' . '&amp;paged=' . '1' . '&amp;s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ),
2381
									$this->get_text( 'deactivate-plugin-title' ),
2382
									' - ' . $this->get_text( 'deactivate-plugin-desc' )
2383
								)
2384
							)
2385
						);
2386
						break;
2387
					case 'cloudflare_ddos_protection':
2388
						$message = sprintf(
2389
							$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2390
							$this->get_text( 'cloudflare-blocks-connection-message' ) . ' ' .
2391
							$this->get_text( 'happy-to-resolve-issue-asap' ) .
2392
							' %s',
2393
							'<b>' . $this->get_plugin_name() . '</b>',
2394
							sprintf(
2395
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2396
								sprintf(
2397
									'<a class="fs-resolve" data-type="cloudflare" href="#"><b>%s</b></a>%s',
2398
									$this->get_text( 'fix-issue-title' ),
2399
									' - ' . sprintf(
2400
										$this->get_text( 'fix-issue-desc' ),
2401
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2402
									)
2403
								),
2404
								sprintf(
2405
									'<a href="%s" target="_blank"><b>%s</b></a>%s',
2406
									sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
2407
									$this->get_text( 'install-previous-title' ),
2408
									' - ' . $this->get_text( 'install-previous-desc' )
2409
								),
2410
								sprintf(
2411
									'<a href="%s"><b>%s</b></a>%s',
2412
									wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . $this->_plugin_basename . '&amp;plugin_status=' . 'all' . '&amp;paged=' . '1' . '&amp;s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ),
2413
									$this->get_text( 'deactivate-plugin-title' ),
2414
									' - ' . $this->get_text( 'deactivate-plugin-desc' )
2415
								)
2416
							)
2417
						);
2418
						break;
2419
					case 'squid_cache_block':
2420
						$message = sprintf(
2421
							$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2422
							$this->get_text( 'squid-blocks-connection-message' ) .
2423
							' %s',
2424
							'<b>' . $this->get_plugin_name() . '</b>',
2425
							sprintf(
2426
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2427
								sprintf(
2428
									'<a class="fs-resolve" data-type="squid" href="#"><b>%s</b></a>%s',
2429
									$this->get_text( 'squid-no-clue-title' ),
2430
									' - ' . sprintf(
2431
										$this->get_text( 'squid-no-clue-desc' ),
2432
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2433
									)
2434
								),
2435
								sprintf(
2436
									'<b>%s</b> - %s',
2437
									$this->get_text( 'sysadmin-title' ),
2438
									sprintf(
2439
										$this->get_text( 'squid-sysadmin-desc' ),
2440
										// We use a filter since the plugin might require additional API connectivity.
2441
										'<b>' . implode( ', ', $this->apply_filters( 'api_domains', array( 'api.freemius.com', 'wp.freemius.com' ) ) ) . '</b>',
2442
										$this->_module_type
2443
									)
2444
								),
2445
								sprintf(
2446
									'<a href="%s"><b>%s</b></a>%s',
2447
									wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . $this->_plugin_basename . '&amp;plugin_status=' . 'all' . '&amp;paged=' . '1' . '&amp;s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ),
2448
									$this->get_text( 'deactivate-plugin-title' ),
2449
									' - ' . $this->get_text( 'deactivate-plugin-desc' )
2450
								)
2451
							)
2452
						);
2453
						break;
2454
//					default:
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2455
//						$message = $this->get_text( 'connectivity-test-fails-message' );
2456
//						break;
2457
				}
2458
			}
2459
2460
			$message_id = 'failed_connect_api';
2461
			$type       = 'error';
2462
2463
			if ( false === $message ) {
2464
				if ( $is_first_failure ) {
2465
					// First attempt failed.
2466
					$message = sprintf(
2467
						$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2468
						$this->get_text( 'connectivity-test-fails-message' ) . ' ' .
2469
						$this->get_text( 'connectivity-test-maybe-temporary' ) . '<br><br>' .
2470
						'%s',
2471
						'<b>' . $this->get_plugin_name() . '</b>',
2472
						sprintf(
2473
							'<div id="fs_firewall_issue_options">%s %s</div>',
2474
							sprintf(
2475
								'<a  class="button button-primary fs-resolve" data-type="retry_ping" href="#">%s</a>',
2476
								$this->get_text( 'yes-do-your-thing' )
2477
							),
2478
							sprintf(
2479
								'<a href="%s" class="button">%s</a>',
2480
								wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . $this->_plugin_basename . '&amp;plugin_status=' . 'all' . '&amp;paged=' . '1' . '&amp;s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ),
2481
								$this->get_text( 'no-deactivate' )
2482
							)
2483
						)
2484
					);
2485
2486
					$message_id = 'failed_connect_api_first';
2487
					$type       = 'promotion';
2488
				} else {
2489
					// Second connectivity attempt failed.
2490
					$message = sprintf(
2491
						$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2492
						$this->get_text( 'connectivity-test-fails-message' ) . ' ' .
2493
						$this->get_text( 'happy-to-resolve-issue-asap' ) .
2494
						' %s',
2495
						'<b>' . $this->get_plugin_name() . '</b>',
2496
						sprintf(
2497
							'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2498
							sprintf(
2499
								'<a class="fs-resolve" data-type="general" href="#"><b>%s</b></a>%s',
2500
								$this->get_text( 'fix-issue-title' ),
2501
								' - ' . sprintf(
2502
									$this->get_text( 'fix-issue-desc' ),
2503
									'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2504
								)
2505
							),
2506
							sprintf(
2507
								'<a href="%s" target="_blank"><b>%s</b></a>%s',
2508
								sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
2509
								$this->get_text( 'install-previous-title' ),
2510
								' - ' . $this->get_text( 'install-previous-desc' )
2511
							),
2512
							sprintf(
2513
								'<a href="%s"><b>%s</b></a>%s',
2514
								wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . $this->_plugin_basename . '&amp;plugin_status=' . 'all' . '&amp;paged=' . '1' . '&amp;s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ),
2515
								$this->get_text( 'deactivate-plugin-title' ),
2516
								' - ' . $this->get_text( 'deactivate-plugin-desc' )
2517
							)
2518
						)
2519
					);
2520
				}
2521
			}
2522
2523
			$this->_admin_notices->add_sticky(
2524
				$message,
2525
				$message_id,
2526
				$this->get_text( 'oops' ) . '...',
2527
				$type
2528
			);
2529
		}
2530
2531
		/**
2532
		 * Handle user request to resolve connectivity issue.
2533
		 * This method will send an email to Freemius API technical staff for resolution.
2534
		 * The email will contain server's info and installed plugins (might be caching issue).
2535
		 *
2536
		 * @author Vova Feldman (@svovaf)
2537
		 * @since  1.0.9
2538
		 */
2539
		function _email_about_firewall_issue() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2540
			$this->_admin_notices->remove_sticky( 'failed_connect_api' );
2541
2542
			$pong = $this->ping();
2543
2544
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2545
2546
			if ( $is_connected ) {
2547
				$this->store_connectivity_info( $pong, $is_connected );
0 ignored issues
show
Security Bug introduced by
It seems like $pong defined by $this->ping() on line 2542 can also be of type false; however, Freemius::store_connectivity_info() does only seem to accept object, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2548
2549
				echo $this->get_after_plugin_activation_redirect_url();
2550
				exit;
2551
			}
2552
2553
			$current_user = self::_get_current_wp_user();
2554
			$admin_email  = $current_user->user_email;
2555
2556
			$error_type = fs_request_get( 'error_type', 'general' );
2557
2558
			switch ( $error_type ) {
2559
				case 'squid':
2560
					$title = 'Squid ACL Blocking Issue';
2561
					break;
2562
				case 'cloudflare':
2563
					$title = 'CloudFlare Blocking Issue';
2564
					break;
2565
				default:
2566
					$title = 'API Connectivity Issue';
2567
					break;
2568
			}
2569
2570
			$custom_email_sections = array();
2571
2572
			// Add 'API Error' custom email section.
2573
			$custom_email_sections['api_error'] = array(
2574
				'title' => 'API Error',
2575
				'rows'  => array(
2576
					'ping' => array(
2577
						'API Error',
2578
						is_string( $pong ) ? htmlentities( $pong ) : json_encode( $pong )
2579
					),
2580
				)
2581
			);
2582
2583
			// Send email with technical details to resolve API connectivity issues.
2584
			$this->send_email(
2585
				'[email protected]',                              // recipient
2586
				$title . ' [' . $this->get_plugin_name() . ']',  // subject
2587
				$custom_email_sections,
2588
				array( "Reply-To: $admin_email <$admin_email>" ) // headers
2589
			);
2590
2591
			$this->_admin_notices->add_sticky(
2592
				sprintf(
2593
					$this->get_text( 'fix-request-sent-message' ),
2594
					'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2595
				),
2596
				'server_details_sent'
2597
			);
2598
2599
			// Action was taken, tell that API connectivity troubleshooting should be off now.
2600
2601
			echo "1";
2602
			exit;
2603
		}
2604
2605
		/**
2606
		 * Handle connectivity test retry approved by the user.
2607
		 *
2608
		 * @author Vova Feldman (@svovaf)
2609
		 * @since  1.1.7.4
2610
		 */
2611
		function _retry_connectivity_test() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2612
			$this->_admin_notices->remove_sticky( 'failed_connect_api_first' );
2613
2614
			$pong = $this->ping();
2615
2616
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2617
2618
			if ( $is_connected ) {
2619
				$this->store_connectivity_info( $pong, $is_connected );
0 ignored issues
show
Security Bug introduced by
It seems like $pong defined by $this->ping() on line 2614 can also be of type false; however, Freemius::store_connectivity_info() does only seem to accept object, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2620
2621
				echo $this->get_after_plugin_activation_redirect_url();
2622
			} else {
2623
				// Add connectivity issue message after 2nd failed attempt.
2624
				$this->_add_connectivity_issue_message( $pong, false );
2625
2626
				echo "1";
2627
			}
2628
2629
			exit;
2630
		}
2631
2632
		static function _add_firewall_issues_javascript() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2633
			$params = array();
2634
			fs_require_once_template( 'firewall-issues-js.php', $params );
2635
		}
2636
2637
		#endregion
2638
2639
		#----------------------------------------------------------------------------------
2640
		#region Email
2641
		#----------------------------------------------------------------------------------
2642
2643
		/**
2644
		 * Generates and sends an HTML email with customizable sections.
2645
		 *
2646
		 * @author Leo Fajardo (@leorw)
2647
		 * @since  1.1.2
2648
		 *
2649
		 * @param string $to_address
2650
		 * @param string $subject
2651
		 * @param array  $sections
2652
		 * @param array  $headers
2653
		 *
2654
		 * @return bool Whether the email contents were sent successfully.
2655
		 */
2656
		private function send_email(
2657
			$to_address,
2658
			$subject,
2659
			$sections = array(),
2660
			$headers = array()
2661
		) {
2662
			$default_sections = $this->get_email_sections();
2663
2664
			// Insert new sections or replace the default email sections.
2665
			if ( is_array( $sections ) && ! empty( $sections ) ) {
2666
				foreach ( $sections as $section_id => $custom_section ) {
2667
					if ( ! isset( $default_sections[ $section_id ] ) ) {
2668
						// If the section does not exist, add it.
2669
						$default_sections[ $section_id ] = $custom_section;
2670
					} else {
2671
						// If the section already exists, override it.
2672
						$current_section = $default_sections[ $section_id ];
2673
2674
						// Replace the current section's title if a custom section title exists.
2675
						if ( isset( $custom_section['title'] ) ) {
2676
							$current_section['title'] = $custom_section['title'];
2677
						}
2678
2679
						// Insert new rows under the current section or replace the default rows.
2680
						if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) {
2681
							foreach ( $custom_section['rows'] as $row_id => $row ) {
2682
								$current_section['rows'][ $row_id ] = $row;
2683
							}
2684
						}
2685
2686
						$default_sections[ $section_id ] = $current_section;
2687
					}
2688
				}
2689
			}
2690
2691
			$vars    = array( 'sections' => $default_sections );
2692
			$message = fs_get_template( 'email.php', $vars );
2693
2694
			// Set the type of email to HTML.
2695
			$headers[] = 'Content-type: text/html; charset=UTF-8';
2696
2697
			$header_string = implode( "\r\n", $headers );
2698
2699
			return wp_mail(
2700
				$to_address,
2701
				$subject,
2702
				$message,
2703
				$header_string
2704
			);
2705
		}
2706
2707
		/**
2708
		 * Generates the data for the sections of the email content.
2709
		 *
2710
		 * @author Leo Fajardo (@leorw)
2711
		 * @since  1.1.2
2712
		 *
2713
		 * @return array
2714
		 */
2715
		private function get_email_sections() {
0 ignored issues
show
Coding Style introduced by
get_email_sections uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2716
			// Retrieve the current user's information so that we can get the user's email, first name, and last name below.
2717
			$current_user = self::_get_current_wp_user();
2718
2719
			// Retrieve the cURL version information so that we can get the version number below.
2720
			$curl_version_information = curl_version();
2721
2722
			$active_plugin = self::get_active_plugins();
2723
2724
			// Generate the list of active plugins separated by new line.
2725
			$active_plugin_string = '';
2726
			foreach ( $active_plugin as $plugin ) {
2727
				$active_plugin_string .= sprintf(
2728
					'<a href="%s">%s</a> [v%s]<br>',
2729
					$plugin['PluginURI'],
2730
					$plugin['Name'],
2731
					$plugin['Version']
2732
				);
2733
			}
2734
2735
			$server_ip = WP_FS__REMOTE_ADDR;
2736
2737
			// Add PHP info for deeper investigation.
2738
			ob_start();
2739
			phpinfo();
2740
			$php_info = ob_get_clean();
2741
2742
			$api_domain = substr( FS_API__ADDRESS, strpos( FS_API__ADDRESS, ':' ) + 3 );
2743
2744
			// Generate the default email sections.
2745
			$sections = array(
2746
				'sdk'      => array(
2747
					'title' => 'SDK',
2748
					'rows'  => array(
2749
						'fs_version'   => array( 'FS Version', $this->version ),
2750
						'curl_version' => array( 'cURL Version', $curl_version_information['version'] )
2751
					)
2752
				),
2753
				'plugin'   => array(
2754
					'title' => ucfirst( $this->get_module_type() ),
2755
					'rows'  => array(
2756
						'name'    => array( 'Name', $this->get_plugin_name() ),
2757
						'version' => array( 'Version', $this->get_plugin_version() )
2758
					)
2759
				),
2760
				'api'      => array(
2761
					'title' => 'API Subdomain',
2762
					'rows'  => array(
2763
						'dns' => array(
2764
							'DNS_CNAME',
2765
							function_exists( 'dns_get_record' ) ?
2766
								var_export( dns_get_record( $api_domain, DNS_CNAME ), true ) :
2767
								'dns_get_record() disabled/blocked'
2768
						),
2769
						'ip'  => array(
2770
							'IP',
2771
							function_exists( 'gethostbyname' ) ?
2772
								gethostbyname( $api_domain ) :
2773
								'gethostbyname() disabled/blocked'
2774
						),
2775
					),
2776
				),
2777
				'site'     => array(
2778
					'title' => 'Site',
2779
					'rows'  => array(
2780
						'unique_id'   => array( 'Unique ID', $this->get_anonymous_id() ),
2781
						'address'     => array( 'Address', site_url() ),
2782
						'host'        => array(
2783
							'HTTP_HOST',
2784
							( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' )
2785
						),
2786
						'hosting'     => array(
2787
							'Hosting Company' => fs_request_has( 'hosting_company' ) ?
2788
								fs_request_get( 'hosting_company' ) :
2789
								'Unknown',
2790
						),
2791
						'server_addr' => array(
2792
							'SERVER_ADDR',
2793
							'<a href="http://www.projecthoneypot.org/ip_' . $server_ip . '">' . $server_ip . '</a>'
2794
						)
2795
					)
2796
				),
2797
				'user'     => array(
2798
					'title' => 'User',
2799
					'rows'  => array(
2800
						'email' => array( 'Email', $current_user->user_email ),
2801
						'first' => array( 'First', $current_user->user_firstname ),
2802
						'last'  => array( 'Last', $current_user->user_lastname )
2803
					)
2804
				),
2805
				'plugins'  => array(
2806
					'title' => 'Plugins',
2807
					'rows'  => array(
2808
						'active_plugins' => array( 'Active Plugins', $active_plugin_string )
2809
					)
2810
				),
2811
				'php_info' => array(
2812
					'title' => 'PHP Info',
2813
					'rows'  => array(
2814
						'info' => array( $php_info )
2815
					),
2816
				)
2817
			);
2818
2819
			// Allow the sections to be modified by other code.
2820
			$sections = $this->apply_filters( 'email_template_sections', $sections );
2821
2822
			return $sections;
2823
		}
2824
2825
		#endregion
2826
2827
		#----------------------------------------------------------------------------------
2828
		#region Initialization
2829
		#----------------------------------------------------------------------------------
2830
2831
		/**
2832
		 * Init plugin's Freemius instance.
2833
		 *
2834
		 * @author Vova Feldman (@svovaf)
2835
		 * @since  1.0.1
2836
		 *
2837
		 * @param number $id
2838
		 * @param string $public_key
2839
		 * @param bool   $is_live
2840
		 * @param bool   $is_premium
2841
		 */
2842
		function init( $id, $public_key, $is_live = true, $is_premium = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2843
			$this->_logger->entrance();
2844
2845
			$this->dynamic_init( array(
2846
				'id'         => $id,
2847
				'public_key' => $public_key,
2848
				'is_live'    => $is_live,
2849
				'is_premium' => $is_premium,
2850
			) );
2851
		}
2852
2853
		/**
2854
		 * Dynamic initiator, originally created to support initiation
2855
		 * with parent_id for add-ons.
2856
		 *
2857
		 * @author Vova Feldman (@svovaf)
2858
		 * @since  1.0.6
2859
		 *
2860
		 * @param array $plugin_info
2861
		 *
2862
		 * @throws Freemius_Exception
2863
		 */
2864
		function dynamic_init( array $plugin_info ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2865
			$this->_logger->entrance();
2866
2867
			$this->parse_settings( $plugin_info );
2868
2869
            if ( $this->has_affiliate_program() ) {
2870
                $this->fetch_affiliate_and_terms();
2871
            }
2872
2873
            if ( ! self::is_ajax() ) {
2874
                if ( ! $this->is_addon() || $this->is_only_premium() ) {
2875
                    add_action( 'admin_menu', array( &$this, '_prepare_admin_menu' ), WP_FS__LOWEST_PRIORITY );
2876
                }
2877
            }
2878
2879
            if ( $this->should_stop_execution() ) {
2880
				return;
2881
			}
2882
2883
			if ( ! $this->is_registered() ) {
2884
				if ( $this->is_anonymous() ) {
2885
					// If user skipped, no need to test connectivity.
2886
					$this->_has_api_connection = true;
2887
					$this->_is_on              = true;
2888
				} else {
2889
					if ( ! $this->has_api_connectivity() ) {
2890
						if ( $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) ||
2891
						     $this->_admin_notices->has_sticky( 'failed_connect_api' )
2892
						) {
2893
							if ( ! $this->_enable_anonymous || $this->is_premium() ) {
2894
								// If anonymous mode is disabled, add firewall admin-notice message.
2895
								add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) );
2896
2897
								$this->add_ajax_action( 'resolve_firewall_issues', array(
2898
									&$this,
2899
									'_email_about_firewall_issue'
2900
								) );
2901
2902
								$this->add_ajax_action( 'retry_connectivity_test', array(
2903
									&$this,
2904
									'_retry_connectivity_test'
2905
								) );
2906
							}
2907
						}
2908
2909
						return;
2910
					} else {
2911
						$this->_admin_notices->remove_sticky( array(
2912
							'failed_connect_api_first',
2913
							'failed_connect_api',
2914
						) );
2915
2916
						if ( $this->_anonymous_mode ) {
2917
							// Simulate anonymous mode.
2918
							$this->_is_anonymous = true;
2919
						}
2920
					}
2921
				}
2922
2923
				// Check if Freemius is on for the current plugin.
2924
				// This MUST be executed after all the plugin variables has been loaded.
2925
				if ( ! $this->is_on() ) {
2926
					return;
2927
				}
2928
			}
2929
2930
			if ( $this->has_api_connectivity() ) {
2931
				if ( self::is_cron() ) {
2932
					$this->hook_callback_to_sync_cron();
2933
				} else if ( $this->is_user_in_admin() ) {
2934
					/**
2935
					 * Schedule daily data sync cron if:
2936
					 *
2937
					 *  1. User opted-in (for tracking).
2938
					 *  2. If skipped, but later upgraded (opted-in via upgrade).
2939
					 *
2940
					 * @author Vova Feldman (@svovaf)
2941
					 * @since  1.1.7.3
2942
					 *
2943
					 */
2944
					if ( $this->is_registered() ) {
2945
						if ( ! $this->is_sync_cron_on() && $this->is_tracking_allowed() ) {
2946
							$this->schedule_sync_cron();
2947
						}
2948
					}
2949
2950
					/**
2951
					 * Check if requested for manual blocking background sync.
2952
					 */
2953
					if ( fs_request_has( 'background_sync' ) ) {
2954
						$this->run_manual_sync();
2955
					}
2956
				}
2957
			}
2958
2959
			if ( $this->is_registered() ) {
2960
				$this->hook_callback_to_install_sync();
2961
			}
2962
2963
			if ( $this->is_addon() ) {
2964
				if ( $this->is_parent_plugin_installed() ) {
2965
					// Link to parent FS.
2966
					$this->_parent = self::get_instance_by_id( $this->_plugin->parent_plugin_id );
0 ignored issues
show
Documentation Bug introduced by
It seems like self::get_instance_by_id...ugin->parent_plugin_id) can also be of type false. However, the property $_parent is declared as type object<Freemius>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2967
2968
					// Get parent plugin reference.
2969
					$this->_parent_plugin = $this->_parent->get_plugin();
2970
				}
2971
			}
2972
2973
			if ( $this->is_user_in_admin() ) {
2974
				if ( self::is_plugins_page() && $this->is_plugin() ) {
2975
					$this->hook_plugin_action_links();
2976
				}
2977
2978
				if ( $this->is_addon() ) {
2979
					if ( ! $this->is_parent_plugin_installed() ) {
2980
						$parent_name = $this->get_option( $plugin_info, 'parent_name', null );
2981
2982
						if ( isset( $plugin_info['parent'] ) ) {
2983
							$parent_name = $this->get_option( $plugin_info['parent'], 'name', null );
0 ignored issues
show
Documentation introduced by
$plugin_info['parent'] is of type string, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2984
						}
2985
2986
						$this->_admin_notices->add(
2987
							( ! empty( $parent_name ) ?
2988
								sprintf( $this->get_text( 'addon-x-cannot-run-without-y' ), $this->get_plugin_name(), $parent_name ) :
2989
								sprintf( $this->get_text( 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() )
2990
							),
2991
							$this->get_text( 'oops' ) . '...',
2992
							'error'
2993
						);
2994
2995
						return;
2996
					} else {
2997
						if ( $this->_parent->is_registered() && ! $this->is_registered() ) {
2998
							// If parent plugin activated, automatically install add-on for the user.
2999
							$this->_activate_addon_account( $this->_parent );
0 ignored issues
show
Security Bug introduced by
It seems like $this->_parent can also be of type false; however, Freemius::_activate_addon_account() does only seem to accept object<Freemius>, did you maybe forget to handle an error condition?
Loading history...
3000
						} else if ( ! $this->_parent->is_registered() && $this->is_registered() ) {
3001
							// If add-on activated and parent not, automatically install parent for the user.
3002
							$this->activate_parent_account( $this->_parent );
0 ignored issues
show
Security Bug introduced by
It seems like $this->_parent can also be of type false; however, Freemius::activate_parent_account() does only seem to accept object<Freemius>, did you maybe forget to handle an error condition?
Loading history...
3003
						}
3004
3005
						// @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic.
3006
						if ( $this->is_premium() ) {
3007
							// Remove add-on download admin-notice.
3008
							$this->_parent->_admin_notices->remove_sticky( array(
3009
								'addon_plan_upgraded_' . $this->_slug,
3010
								'no_addon_license_' . $this->_slug,
3011
							) );
3012
						}
3013
3014
//						$this->deactivate_premium_only_addon_without_license();
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3015
					}
3016
				} else {
3017
					if ( $this->has_addons() &&
3018
					     'plugin-information' === fs_request_get( 'tab', false ) &&
3019
					     $this->get_id() == fs_request_get( 'parent_plugin_id', false )
3020
					) {
3021
						require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php';
3022
3023
						new FS_Plugin_Info_Dialog( $this );
3024
					}
3025
				}
3026
3027
				add_action( 'admin_init', array( &$this, '_admin_init_action' ) );
3028
3029
//				if ( $this->is_registered() ||
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3030
//				     $this->is_anonymous() ||
3031
//				     $this->is_pending_activation()
3032
//				) {
3033
//					$this->_init_admin();
3034
//				}
3035
			}
3036
3037
			/**
3038
			 * Should be called outside `$this->is_user_in_admin()` scope
3039
			 * because the updater has some logic that needs to be executed
3040
			 * during AJAX calls.
3041
			 *
3042
			 * Currently we need to hook to the `http_request_host_is_external` filter.
3043
			 * In the future, there might be additional logic added.
3044
			 *
3045
			 * @author Vova Feldman
3046
			 * @since  1.2.1.6
3047
			 */
3048
			if ( $this->is_premium() && $this->has_release_on_freemius() ) {
3049
				new FS_Plugin_Updater( $this );
3050
			}
3051
3052
			$this->do_action( 'initiated' );
3053
3054
			if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) {
3055
				if ( isset( $this->_storage->prev_is_premium ) ) {
3056
					$this->apply_filters(
3057
						'after_code_type_change',
3058
						// New code type.
3059
						$this->_plugin->is_premium
3060
					);
3061
				} else {
3062
					// Set for code type for the first time.
3063
					$this->_storage->prev_is_premium = $this->_plugin->is_premium;
3064
				}
3065
			}
3066
3067
			if ( ! $this->is_addon() ) {
3068
				if ( $this->is_registered() ) {
3069
					// Fix for upgrade from versions < 1.0.9.
3070
					if ( ! isset( $this->_storage->activation_timestamp ) ) {
3071
						$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
3072
					}
3073
3074
					$this->do_action( 'after_init_plugin_registered' );
3075
				} else if ( $this->is_anonymous() ) {
3076
					$this->do_action( 'after_init_plugin_anonymous' );
3077
				} else if ( $this->is_pending_activation() ) {
3078
					$this->do_action( 'after_init_plugin_pending_activations' );
3079
				}
3080
			} else {
3081
				if ( $this->is_registered() ) {
3082
					$this->do_action( 'after_init_addon_registered' );
3083
				} else if ( $this->is_anonymous() ) {
3084
					$this->do_action( 'after_init_addon_anonymous' );
3085
				} else if ( $this->is_pending_activation() ) {
3086
					$this->do_action( 'after_init_addon_pending_activations' );
3087
				}
3088
			}
3089
		}
3090
3091
		/**
3092
		 * @author Leo Fajardo (@leorw)
3093
		 *
3094
		 * @since  1.2.1.5
3095
		 */
3096
		function _stop_tracking_callback() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3097
			$this->_logger->entrance();
3098
3099
			$this->check_ajax_referer( 'stop_tracking' );
3100
3101
			$result = $this->stop_tracking();
3102
3103
			if ( true === $result ) {
3104
				self::shoot_ajax_success();
3105
			}
3106
3107
			$this->_logger->api_error( $result );
3108
3109
			self::shoot_ajax_failure(
3110
				$this->get_text( 'unexpected-api-error' ) .
3111
				( $this->is_api_error( $result ) && isset( $result->error ) ?
3112
					$result->error->message :
3113
					var_export( $result, true ) )
3114
			);
3115
		}
3116
3117
		/**
3118
		 * @author Leo Fajardo (@leorw)
3119
		 * @since  1.2.1.5
3120
		 */
3121
		function _allow_tracking_callback() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3122
			$this->_logger->entrance();
3123
3124
			$this->check_ajax_referer( 'allow_tracking' );
3125
3126
			$result = $this->allow_tracking();
3127
3128
			if ( true === $result ) {
3129
				self::shoot_ajax_success();
3130
			}
3131
3132
			$this->_logger->api_error( $result );
3133
3134
			self::shoot_ajax_failure(
3135
				$this->get_text( 'unexpected-api-error' ) .
3136
				( $this->is_api_error( $result ) && isset( $result->error ) ?
3137
					$result->error->message :
3138
					var_export( $result, true ) )
3139
			);
3140
		}
3141
3142
		/**
3143
		 * Opt-out from usage tracking.
3144
		 *
3145
		 * Note: This will not delete the account information but will stop all tracking.
3146
		 *
3147
		 * Returns:
3148
		 *  1. FALSE  - If the user never opted-in.
3149
		 *  2. TRUE   - If successfully opted-out.
3150
		 *  3. object - API result on failure.
3151
		 *
3152
		 * @author Leo Fajardo (@leorw)
3153
		 * @since  1.2.1.5
3154
		 *
3155
		 * @return bool|object
3156
		 */
3157
		function stop_tracking() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3158
			$this->_logger->entrance();
3159
3160
			if ( ! $this->is_registered() ) {
3161
				// User never opted-in.
3162
				return false;
3163
			}
3164
3165
			if ( $this->is_tracking_prohibited() ) {
3166
				// Already disconnected.
3167
				return true;
3168
			}
3169
3170
			// Send update to FS.
3171
			$result = $this->get_api_site_scope()->call( '/?fields=is_disconnected', 'put', array(
3172
				'is_disconnected' => true
3173
			) );
3174
3175
			if ( ! $this->is_api_result_entity( $result ) ||
3176
			     ! isset( $result->is_disconnected ) ||
3177
			     ! $result->is_disconnected
3178
			) {
3179
				$this->_logger->api_error( $result );
3180
3181
				return $result;
3182
			}
3183
3184
			$this->_site->is_disconnected = $result->is_disconnected;
3185
			$this->_store_site();
3186
3187
			$this->clear_sync_cron();
3188
3189
			// Successfully disconnected.
3190
			return true;
3191
		}
3192
3193
		/**
3194
		 * Opt-in back into usage tracking.
3195
		 *
3196
		 * Note: This will only work if the user opted-in previously.
3197
		 *
3198
		 * Returns:
3199
		 *  1. FALSE  - If the user never opted-in.
3200
		 *  2. TRUE   - If successfully opted-in back to usage tracking.
3201
		 *  3. object - API result on failure.
3202
		 *
3203
		 * @author Leo Fajardo (@leorw)
3204
		 * @since  1.2.1.5
3205
		 *
3206
		 * @return bool|object
3207
		 */
3208
		function allow_tracking() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3209
			$this->_logger->entrance();
3210
3211
			if ( ! $this->is_registered() ) {
3212
				// User never opted-in.
3213
				return false;
3214
			}
3215
3216
			if ( $this->is_tracking_allowed() ) {
3217
				// Tracking already allowed.
3218
				return true;
3219
			}
3220
3221
			$result = $this->get_api_site_scope()->call( '/?is_disconnected', 'put', array(
3222
				'is_disconnected' => false
3223
			) );
3224
3225
			if ( ! $this->is_api_result_entity( $result ) ||
3226
			     ! isset( $result->is_disconnected ) ||
3227
			     $result->is_disconnected
3228
			) {
3229
				$this->_logger->api_error( $result );
3230
3231
				return $result;
3232
			}
3233
3234
			$this->_site->is_disconnected = $result->is_disconnected;
3235
			$this->_store_site();
3236
3237
			$this->schedule_sync_cron();
3238
3239
			// Successfully reconnected.
3240
			return true;
3241
		}
3242
3243
		/**
3244
		 * If user opted-in and later disabled usage-tracking,
3245
		 * re-allow tracking for licensing and updates.
3246
		 *
3247
		 * @author Leo Fajardo (@leorw)
3248
		 *
3249
		 * @since  1.2.1.5
3250
		 */
3251
		private function reconnect_locally() {
3252
			$this->_logger->entrance();
3253
3254
			if ( $this->is_tracking_prohibited() &&
3255
			     $this->is_registered()
3256
			) {
3257
				$this->_site->is_disconnected = false;
3258
				$this->_store_site();
3259
			}
3260
		}
3261
3262
		/**
3263
		 * Parse plugin's settings (as defined by the plugin dev).
3264
		 *
3265
		 * @author Vova Feldman (@svovaf)
3266
		 * @since  1.1.7.3
3267
		 *
3268
		 * @param array $plugin_info
3269
		 *
3270
		 * @throws \Freemius_Exception
3271
		 */
3272
		private function parse_settings( &$plugin_info ) {
3273
			$this->_logger->entrance();
3274
3275
			$id          = $this->get_numeric_option( $plugin_info, 'id', false );
3276
			$public_key  = $this->get_option( $plugin_info, 'public_key', false );
3277
			$secret_key  = $this->get_option( $plugin_info, 'secret_key', null );
3278
			$parent_id   = $this->get_numeric_option( $plugin_info, 'parent_id', null );
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3279
			$parent_name = $this->get_option( $plugin_info, 'parent_name', null );
0 ignored issues
show
Unused Code introduced by
$parent_name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3280
3281
			/**
3282
			 * @author Vova Feldman (@svovaf)
3283
			 * @since  1.1.9 Try to pull secret key from external config.
3284
			 */
3285
			if ( is_null( $secret_key ) && defined( "WP_FS__{$this->_slug}_SECRET_KEY" ) ) {
3286
				$secret_key = constant( "WP_FS__{$this->_slug}_SECRET_KEY" );
3287
			}
3288
3289
			if ( isset( $plugin_info['parent'] ) ) {
3290
				$parent_id = $this->get_numeric_option( $plugin_info['parent'], 'id', null );
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3291
//				$parent_slug       = $this->get_option( $plugin_info['parent'], 'slug', null );
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3292
//				$parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null );
3293
//				$parent_name = $this->get_option( $plugin_info['parent'], 'name', null );
3294
			}
3295
3296
			if ( false === $id ) {
3297
				throw new Freemius_Exception( array(
3298
					'error' => array(
3299
						'type'    => 'ParameterNotSet',
3300
						'message' => 'Plugin id parameter is not set.',
3301
						'code'    => 'plugin_id_not_set',
3302
						'http'    => 500,
3303
					)
3304
				) );
3305
			}
3306
			if ( false === $public_key ) {
3307
				throw new Freemius_Exception( array(
3308
					'error' => array(
3309
						'type'    => 'ParameterNotSet',
3310
						'message' => 'Plugin public_key parameter is not set.',
3311
						'code'    => 'plugin_public_key_not_set',
3312
						'http'    => 500,
3313
					)
3314
				) );
3315
			}
3316
3317
			$plugin = ( $this->_plugin instanceof FS_Plugin ) ?
3318
				$this->_plugin :
3319
				new FS_Plugin();
3320
3321
			$plugin->update( array(
3322
				'id'                   => $id,
3323
				'type'                 => $this->get_option( $plugin_info, 'type', $this->_module_type),
3324
				'public_key'           => $public_key,
3325
				'slug'                 => $this->_slug,
3326
				'parent_plugin_id'     => $parent_id,
3327
				'version'              => $this->get_plugin_version(),
3328
				'title'                => $this->get_plugin_name(),
3329
				'file'                 => $this->_plugin_basename,
3330
				'is_premium'           => $this->get_bool_option( $plugin_info, 'is_premium', true ),
3331
				'is_live'              => $this->get_bool_option( $plugin_info, 'is_live', true ),
3332
				'affiliate_moderation' => $this->get_option( $plugin_info, 'has_affiliation' ),
3333
			) );
3334
3335
			if ( $plugin->is_updated() ) {
3336
				// Update plugin details.
3337
				$this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->store( $plugin );
0 ignored issues
show
Documentation Bug introduced by
It seems like \FS_Plugin_Manager::inst...ule_id)->store($plugin) can also be of type boolean. However, the property $_plugin is declared as type object<FS_Plugin>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3338
			}
3339
			// Set the secret key after storing the plugin, we don't want to store the key in the storage.
3340
			$this->_plugin->secret_key = $secret_key;
3341
3342
			if ( ! isset( $plugin_info['menu'] ) ) {
3343
				$plugin_info['menu'] = array();
3344
3345
				if ( ! empty( $this->_storage->sdk_last_version ) &&
3346
				     version_compare( $this->_storage->sdk_last_version, '1.1.2', '<=' )
3347
				) {
3348
					// Backward compatibility to 1.1.2
3349
					$plugin_info['menu']['slug'] = isset( $plugin_info['menu_slug'] ) ?
3350
						$plugin_info['menu_slug'] :
3351
						$this->_slug;
3352
				}
3353
			}
3354
3355
			$this->_menu = FS_Admin_Menu_Manager::instance(
3356
				$this->_module_id,
3357
				$this->_module_type,
3358
				$this->get_unique_affix()
3359
			);
3360
3361
			$this->_menu->init( $plugin_info['menu'], $this->is_addon() );
0 ignored issues
show
Bug introduced by
It seems like $plugin_info['menu'] can also be of type string; however, FS_Admin_Menu_Manager::init() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3362
3363
			$this->_has_addons          = $this->get_bool_option( $plugin_info, 'has_addons', false );
3364
			$this->_has_paid_plans      = $this->get_bool_option( $plugin_info, 'has_paid_plans', true );
3365
			$this->_has_premium_version = $this->get_bool_option( $plugin_info, 'has_premium_version', $this->_has_paid_plans );
3366
			$this->_ignore_pending_mode = $this->get_bool_option( $plugin_info, 'ignore_pending_mode', false );
3367
			$this->_is_org_compliant    = $this->get_bool_option( $plugin_info, 'is_org_compliant', true );
3368
			$this->_is_premium_only     = $this->get_bool_option( $plugin_info, 'is_premium_only', false );
3369
			if ( $this->_is_premium_only ) {
3370
				// If premium only plugin, disable anonymous mode.
3371
				$this->_enable_anonymous = false;
3372
				$this->_anonymous_mode   = false;
3373
			} else {
3374
				$this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true );
3375
				$this->_anonymous_mode   = $this->get_bool_option( $plugin_info, 'anonymous_mode', false );
3376
			}
3377
			$this->_permissions = $this->get_option( $plugin_info, 'permissions', array() );
3378
3379
			if ( ! empty( $plugin_info['trial'] ) ) {
3380
				$this->_trial_days = $this->get_numeric_option(
3381
					$plugin_info['trial'],
3382
					'days',
3383
					// Default to 0 - trial without days specification.
3384
					0
0 ignored issues
show
Documentation introduced by
0 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
3385
				);
3386
3387
				$this->_is_trial_require_payment = $this->get_bool_option( $plugin_info['trial'], 'is_require_payment', false );
3388
			}
3389
		}
3390
3391
		/**
3392
		 * @param string[] $options
3393
		 * @param string   $key
3394
		 * @param mixed    $default
3395
		 *
3396
		 * @return bool
3397
		 */
3398
		private function get_option( &$options, $key, $default = false ) {
3399
			return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
3400
		}
3401
3402
		private function get_bool_option( &$options, $key, $default = false ) {
3403
			return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
3404
		}
3405
3406
		private function get_numeric_option( &$options, $key, $default = false ) {
3407
			return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default;
3408
		}
3409
3410
		/**
3411
		 * Gate keeper.
3412
		 *
3413
		 * @author Vova Feldman (@svovaf)
3414
		 * @since  1.1.7.3
3415
		 *
3416
		 * @return bool
3417
		 */
3418
		private function should_stop_execution() {
3419
			if ( empty( $this->_storage->was_plugin_loaded ) ) {
3420
				/**
3421
				 * Don't execute Freemius until plugin was fully loaded at least once,
3422
				 * to give the opportunity for the activation hook to run before pinging
3423
				 * the API for connectivity test. This logic is relevant for the
3424
				 * identification of new plugin install vs. plugin update.
3425
				 *
3426
				 * @author Vova Feldman (@svovaf)
3427
				 * @since  1.1.9
3428
				 */
3429
				return true;
3430
			}
3431
3432
			if ( $this->is_activation_mode() ) {
3433
				if ( ! is_admin() ) {
3434
					/**
3435
					 * If in activation mode, don't execute Freemius outside of the
3436
					 * admin dashboard.
3437
					 *
3438
					 * @author Vova Feldman (@svovaf)
3439
					 * @since  1.1.7.3
3440
					 */
3441
					return true;
3442
				}
3443
3444
				if ( ! WP_FS__IS_HTTP_REQUEST ) {
3445
					/**
3446
					 * If in activation and executed without HTTP context (e.g. CLI, Cronjob),
3447
					 * then don't start Freemius.
3448
					 *
3449
					 * @author Vova Feldman (@svovaf)
3450
					 * @since  1.1.6.3
3451
					 *
3452
					 * @link   https://wordpress.org/support/topic/errors-in-the-freemius-class-when-running-in-wordpress-in-cli
3453
					 */
3454
					return true;
3455
				}
3456
3457
				if ( self::is_cron() ) {
3458
					/**
3459
					 * If in activation mode, don't execute Freemius during wp crons
3460
					 * (wp crons have HTTP context - called as HTTP request).
3461
					 *
3462
					 * @author Vova Feldman (@svovaf)
3463
					 * @since  1.1.7.3
3464
					 */
3465
					return true;
3466
				}
3467
3468
				if ( self::is_ajax() &&
3469
				     ! $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) &&
3470
				     ! $this->_admin_notices->has_sticky( 'failed_connect_api' )
3471
				) {
3472
					/**
3473
					 * During activation, if running in AJAX mode, unless there's a sticky
3474
					 * connectivity issue notice, don't run Freemius.
3475
					 *
3476
					 * @author Vova Feldman (@svovaf)
3477
					 * @since  1.1.7.3
3478
					 */
3479
					return true;
3480
				}
3481
			}
3482
3483
			return false;
3484
		}
3485
3486
		/**
3487
		 * Triggered after code type has changed.
3488
		 *
3489
		 * @author Vova Feldman (@svovaf)
3490
		 * @since  1.1.9.1
3491
		 */
3492
		function _after_code_type_change() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3493
			$this->_logger->entrance();
3494
3495
			if ( $this->is_theme() ) {
3496
				// Expire the cache of the previous tabs since the theme may
3497
				// have setting updates after code type has changed.
3498
				$this->_cache->expire( 'tabs' );
3499
				$this->_cache->expire( 'tabs_stylesheets' );
3500
			}
3501
3502
			if ( $this->is_registered() ) {
3503
				if ( ! $this->is_addon() ) {
3504
					add_action(
3505
						is_admin() ? 'admin_init' : 'init',
3506
						array( &$this, '_plugin_code_type_changed' )
3507
					);
3508
				}
3509
3510
				if ( $this->is_premium() ) {
3511
					// Purge cached payments after switching to the premium version.
3512
					// @todo This logic doesn't handle purging the cache for serviceware module upgrade.
3513
					$this->get_api_user_scope()->purge_cache( "/plugins/{$this->_module_id}/payments.json?include_addons=true" );
3514
				}
3515
			}
3516
		}
3517
3518
		/**
3519
		 * Handles plugin's code type change (free <--> premium).
3520
		 *
3521
		 * @author Vova Feldman (@svovaf)
3522
		 * @since  1.0.9
3523
		 */
3524
		function _plugin_code_type_changed() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3525
			$this->_logger->entrance();
3526
3527
			if ( $this->is_premium() ) {
3528
				$this->reconnect_locally();
3529
3530
				// Activated premium code.
3531
				$this->do_action( 'after_premium_version_activation' );
3532
3533
				// Remove all sticky messages related to download of the premium version.
3534
				$this->_admin_notices->remove_sticky( array(
3535
					'trial_started',
3536
					'plan_upgraded',
3537
					'plan_changed',
3538
					'license_activated',
3539
				) );
3540
3541
				if ( ! $this->is_only_premium() ) {
3542
                    $this->_admin_notices->add_sticky(
3543
                        sprintf( $this->get_text( 'premium-activated-message' ), $this->_module_type ),
3544
                        'premium_activated',
3545
                        $this->get_text( 'woot' ) . '!'
3546
                    );
3547
                }
3548
			} else {
3549
				// Remove sticky message related to premium code activation.
3550
				$this->_admin_notices->remove_sticky( 'premium_activated' );
3551
3552
				// Activated free code (after had the premium before).
3553
				$this->do_action( 'after_free_version_reactivation' );
3554
3555
				if ( $this->is_paying() && ! $this->is_premium() ) {
3556
					$this->_admin_notices->add_sticky(
3557
						sprintf(
3558
							$this->get_text( 'you-have-x-license' ),
3559
							$this->_site->plan->title
3560
						) . $this->get_complete_upgrade_instructions(),
3561
						'plan_upgraded',
3562
						$this->get_text( 'yee-haw' ) . '!'
3563
					);
3564
				}
3565
			}
3566
3567
			// Schedule code type changes event.
3568
			$this->schedule_install_sync();
3569
3570
			/**
3571
			 * Unregister the uninstall hook for the other version of the plugin (with different code type) to avoid
3572
			 * triggering a fatal error when uninstalling that plugin. For example, after deactivating the "free" version
3573
			 * of a specific plugin, its uninstall hook should be unregistered after the "premium" version has been
3574
			 * activated. If we don't do that, a fatal error will occur when we try to uninstall the "free" version since
3575
			 * the main file of the "free" version will be loaded first before calling the hooked callback. Since the
3576
			 * free and premium versions are almost identical (same class or have same functions), a fatal error like
3577
			 * "Cannot redeclare class MyClass" or "Cannot redeclare my_function()" will occur.
3578
			 */
3579
			$this->unregister_uninstall_hook();
3580
3581
			$this->clear_module_main_file_cache();
3582
3583
			// Update is_premium of latest version.
3584
			$this->_storage->prev_is_premium = $this->_plugin->is_premium;
3585
		}
3586
3587
		#endregion
3588
3589
		#----------------------------------------------------------------------------------
3590
		#region Add-ons
3591
		#----------------------------------------------------------------------------------
3592
3593
		/**
3594
		 * Check if add-on installed and activated on site.
3595
		 *
3596
		 * @author Vova Feldman (@svovaf)
3597
		 * @since  1.0.6
3598
		 *
3599
		 * @param string|number $id_or_slug
3600
		 * @param bool|null     $is_premium Since 1.2.1.7 can check for specified add-on version.
3601
		 *
3602
		 * @return bool
3603
		 */
3604
		function is_addon_activated( $id_or_slug, $is_premium = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3605
			$this->_logger->entrance();
3606
3607
			$addon_id     = self::get_module_id( $id_or_slug );
3608
			$is_activated = self::has_instance( $addon_id );
3609
3610
			if ( ! $is_activated ) {
3611
				return false;
3612
			}
3613
3614
			if ( is_bool( $is_premium ) ) {
3615
				// Check if the specified code version is activate.
3616
				$addon        = $this->get_addon_instance( $addon_id );
3617
				$is_activated = ( $is_premium === $addon->is_premium() );
3618
			}
3619
3620
			return $is_activated;
3621
		}
3622
3623
		/**
3624
		 * Check if add-on was connected to install
3625
		 *
3626
		 * @author Vova Feldman (@svovaf)
3627
		 * @since  1.1.7
3628
		 *
3629
		 * @param  string|number $id_or_slug
3630
		 *
3631
		 * @return bool
3632
		 */
3633
		function is_addon_connected( $id_or_slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3634
			$this->_logger->entrance();
3635
3636
			$sites = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN );
3637
3638
			$addon_id = self::get_module_id( $id_or_slug );
3639
			$addon    = $this->get_addon( $addon_id );
3640
			$slug     = $addon->slug;
3641
			if ( ! isset( $sites[ $slug ] ) ) {
3642
				return false;
3643
			}
3644
3645
			$site = $sites[ $slug ];
3646
3647
			$plugin = FS_Plugin_Manager::instance( $addon_id )->get();
3648
3649
			if ( $plugin->parent_plugin_id != $this->_plugin->id ) {
3650
				// The given slug do NOT belong to any of the plugin's add-ons.
3651
				return false;
3652
			}
3653
3654
			return ( is_object( $site ) &&
3655
			         is_numeric( $site->id ) &&
3656
			         is_numeric( $site->user_id ) &&
3657
			         is_object( $site->plan )
3658
			);
3659
		}
3660
3661
		/**
3662
		 * Determines if add-on installed.
3663
		 *
3664
		 * NOTE: This is a heuristic and only works if the folder/file named as the slug.
3665
		 *
3666
		 * @author Vova Feldman (@svovaf)
3667
		 * @since  1.0.6
3668
		 *
3669
		 * @param  string|number $id_or_slug
3670
		 *
3671
		 * @return bool
3672
		 */
3673
		function is_addon_installed( $id_or_slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3674
			$this->_logger->entrance();
3675
3676
			$addon_id = self::get_module_id( $id_or_slug );
3677
3678
			return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $addon_id ) ) );
3679
		}
3680
3681
		/**
3682
		 * Get add-on basename.
3683
		 *
3684
		 * @author Vova Feldman (@svovaf)
3685
		 * @since  1.0.6
3686
		 *
3687
		 * @param  string|number $id_or_slug
3688
		 *
3689
		 * @return string
3690
		 */
3691
		function get_addon_basename( $id_or_slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3692
			$addon_id = self::get_module_id( $id_or_slug );
3693
3694
			if ( $this->is_addon_activated( $addon_id ) ) {
3695
				return self::instance( $addon_id )->get_plugin_basename();
3696
			}
3697
3698
			$addon            = $this->get_addon( $addon_id );
3699
			$premium_basename = "{$addon->slug}-premium/{$addon->slug}.php";
3700
3701
			if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_basename ) ) ) {
3702
				return $premium_basename;
3703
			}
3704
3705
			$all_plugins = $this->get_all_plugins();
3706
3707
			foreach ( $all_plugins as $basename => &$data ) {
3708
				if ( $addon->slug === $data['slug'] ||
3709
                    $addon->slug . '-premium' === $data['slug']
3710
				) {
3711
					return $basename;
3712
				}
3713
			}
3714
3715
			$free_basename = "{$addon->slug}/{$addon->slug}.php";
3716
3717
			return $free_basename;
3718
		}
3719
3720
		/**
3721
		 * Get installed add-ons instances.
3722
		 *
3723
		 * @author Vova Feldman (@svovaf)
3724
		 * @since  1.0.6
3725
		 *
3726
		 * @return Freemius[]
3727
		 */
3728
		function get_installed_addons() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3729
			$installed_addons = array();
3730
			foreach ( self::$_instances as $instance ) {
3731
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
3732
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
3733
						$installed_addons[] = $instance;
3734
					}
3735
				}
3736
			}
3737
3738
			return $installed_addons;
3739
		}
3740
3741
		/**
3742
		 * Check if any add-ons of the plugin are installed.
3743
		 *
3744
		 * @author Leo Fajardo (@leorw)
3745
		 * @since  1.1.1
3746
		 *
3747
		 * @return bool
3748
		 */
3749
		function has_installed_addons() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3750
			if ( ! $this->has_addons() ) {
3751
				return false;
3752
			}
3753
3754
			foreach ( self::$_instances as $instance ) {
3755
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
3756
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
3757
						return true;
3758
					}
3759
				}
3760
			}
3761
3762
			return false;
3763
		}
3764
3765
		/**
3766
		 * Tell Freemius that the current plugin is an add-on.
3767
		 *
3768
		 * @author Vova Feldman (@svovaf)
3769
		 * @since  1.0.6
3770
		 *
3771
		 * @param number $parent_plugin_id The parent plugin ID
3772
		 */
3773
		function init_addon( $parent_plugin_id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3774
			$this->_plugin->parent_plugin_id = $parent_plugin_id;
3775
		}
3776
3777
		/**
3778
		 * @author Vova Feldman (@svovaf)
3779
		 * @since  1.0.6
3780
		 *
3781
		 * @return bool
3782
		 */
3783
		function is_addon() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3784
			return isset( $this->_plugin->parent_plugin_id ) && is_numeric( $this->_plugin->parent_plugin_id );
3785
		}
3786
3787
		/**
3788
		 * Deactivate add-on if it's premium only and the user does't have a valid license.
3789
		 *
3790
		 * @param bool $is_after_trial_cancel
3791
		 *
3792
		 * @return bool If add-on was deactivated.
3793
		 */
3794
		private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) {
3795
			if ( ! $this->has_free_plan() &&
3796
			     ! $this->has_features_enabled_license() &&
3797
			     ! $this->_has_premium_license()
3798
			) {
3799
				if ( $this->is_registered() ) {				
3800
					// IF wrapper is turned off because activation_timestamp is currently only stored for plugins (not addons).
3801
	//                if (empty($this->_storage->activation_timestamp) ||
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3802
	//                    (WP_FS__SCRIPT_START_TIME - $this->_storage->activation_timestamp) > 30
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3803
	//                ) {
3804
					/**
3805
					 * @todo When it's first fail, there's no reason to try and re-sync because the licenses were just synced after initial activation.
3806
					 *
3807
					 * Retry syncing the user add-on licenses.
3808
					 */
3809
					// Sync licenses.
3810
					$this->_sync_licenses();
3811
	//                }
3812
3813
					// Try to activate premium license.
3814
					$this->_activate_license( true );
3815
				}
3816
3817
				if ( ! $this->has_free_plan() &&
3818
				     ! $this->has_features_enabled_license() &&
3819
				     ! $this->_has_premium_license()
3820
				) {
3821
					// @todo Check if deactivate plugins also call the deactivation hook.
3822
3823
					$this->_parent->_admin_notices->add_sticky(
3824
						sprintf(
3825
							$this->_parent->get_text( $is_after_trial_cancel ?
3826
								'addon-trial-cancelled-message' :
3827
								'addon-no-license-message'
3828
							),
3829
							'<b>' . $this->_plugin->title . '</b>'
3830
						) . ' ' . sprintf(
3831
							'<a href="%s" aria-label="%s" class="button button-primary" style="margin-left: 10px; vertical-align: middle;">%s &nbsp;&#10140;</a>',
3832
							$this->_parent->addon_url( $this->_slug ),
3833
							esc_attr( sprintf( $this->_parent->get_text( 'more-information-about-x' ), $this->_plugin->title ) ),
3834
							$this->_parent->get_text( 'purchase-license' )
3835
						),
3836
						'no_addon_license_' . $this->_slug,
3837
						( $is_after_trial_cancel ? '' : $this->_parent->get_text( 'oops' ) . '...' ),
3838
						( $is_after_trial_cancel ? 'success' : 'error' )
3839
					);
3840
3841
					deactivate_plugins( array( $this->_plugin_basename ), true );
3842
3843
					return true;
3844
				}
3845
			}
3846
3847
			return false;
3848
		}
3849
3850
		#endregion
3851
3852
		#----------------------------------------------------------------------------------
3853
		#region Sandbox
3854
		#----------------------------------------------------------------------------------
3855
3856
		/**
3857
		 * Set Freemius into sandbox mode for debugging.
3858
		 *
3859
		 * @author Vova Feldman (@svovaf)
3860
		 * @since  1.0.4
3861
		 *
3862
		 * @param string $secret_key
3863
		 */
3864
		function init_sandbox( $secret_key ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3865
			$this->_plugin->secret_key = $secret_key;
3866
3867
			// Update plugin details.
3868
			FS_Plugin_Manager::instance( $this->_module_id )->update( $this->_plugin, true );
3869
		}
3870
3871
		/**
3872
		 * Check if running payments in sandbox mode.
3873
		 *
3874
		 * @author Vova Feldman (@svovaf)
3875
		 * @since  1.0.4
3876
		 *
3877
		 * @return bool
3878
		 */
3879
		function is_payments_sandbox() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3880
			return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key );
3881
		}
3882
3883
		#endregion
3884
3885
		/**
3886
		 * Check if running test vs. live plugin.
3887
		 *
3888
		 * @author Vova Feldman (@svovaf)
3889
		 * @since  1.0.5
3890
		 *
3891
		 * @return bool
3892
		 */
3893
		function is_live() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3894
			return $this->_plugin->is_live;
3895
		}
3896
3897
		/**
3898
		 * Check if the user skipped connecting the account with Freemius.
3899
		 *
3900
		 * @author Vova Feldman (@svovaf)
3901
		 * @since  1.0.7
3902
		 *
3903
		 * @return bool
3904
		 */
3905
		function is_anonymous() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3906
			if ( ! isset( $this->_is_anonymous ) ) {
3907
				if ( ! isset( $this->_storage->is_anonymous ) ) {
3908
					// Not skipped.
3909
					$this->_is_anonymous = false;
3910
				} else if ( is_bool( $this->_storage->is_anonymous ) ) {
3911
					// For back compatibility, since the variable was boolean before.
3912
					$this->_is_anonymous = $this->_storage->is_anonymous;
3913
3914
					// Upgrade stored data format to 1.1.3 format.
3915
					$this->set_anonymous_mode( $this->_storage->is_anonymous );
3916
				} else {
3917
					// Version 1.1.3 and later.
3918
					$this->_is_anonymous = $this->_storage->is_anonymous['is'];
3919
				}
3920
			}
3921
3922
			return $this->_is_anonymous;
3923
		}
3924
3925
		/**
3926
		 * Check if user connected his account and install pending email activation.
3927
		 *
3928
		 * @author Vova Feldman (@svovaf)
3929
		 * @since  1.0.7
3930
		 *
3931
		 * @return bool
3932
		 */
3933
		function is_pending_activation() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3934
			return $this->_storage->get( 'is_pending_activation', false );
3935
		}
3936
3937
		/**
3938
		 * Check if plugin must be WordPress.org compliant.
3939
		 *
3940
		 * @since 1.0.7
3941
		 *
3942
		 * @return bool
3943
		 */
3944
		function is_org_repo_compliant() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3945
			return $this->_is_org_compliant;
3946
		}
3947
3948
		#----------------------------------------------------------------------------------
3949
		#region Daily Sync Cron
3950
		#----------------------------------------------------------------------------------
3951
3952
		/**
3953
		 * @author Vova Feldman (@svovaf)
3954
		 * @since  1.1.7.3
3955
		 */
3956
		private function run_manual_sync() {
3957
			self::require_pluggable_essentials();
3958
3959
			if ( ! $this->is_user_admin() ) {
3960
				return;
3961
			}
3962
3963
			// Run manual sync.
3964
			$this->_sync_cron();
3965
3966
			// Reschedule next cron to run 24 hours from now (performance optimization).
3967
			$this->clear_sync_cron();
3968
3969
			$this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false );
3970
		}
3971
3972
		/**
3973
		 * Data sync cron job. Replaces the background sync non blocking HTTP request
3974
		 * that doesn't halt page loading.
3975
		 *
3976
		 * @author Vova Feldman (@svovaf)
3977
		 * @since  1.1.7.3
3978
		 */
3979
		function _sync_cron() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3980
			$this->_logger->entrance();
3981
3982
			// Store the last time data sync was executed.
3983
			$this->_storage->sync_timestamp = time();
3984
3985
			// Check if API is temporary down.
3986
			if ( FS_Api::is_temporary_down() ) {
3987
				return;
3988
			}
3989
3990
			// @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours.
3991
3992
			if ( $this->is_registered() ) {
3993
				if ( $this->has_paid_plan() ) {
3994
					// Initiate background plan sync.
3995
					$this->_sync_license( true );
3996
3997
					if ( $this->is_paying() ) {
3998
						// Check for premium plugin updates.
3999
						$this->check_updates( true );
4000
					}
4001
				} else {
4002
					// Sync install (only if something changed locally).
4003
					$this->sync_install();
4004
				}
4005
			}
4006
4007
			$this->do_action( 'after_sync_cron' );
4008
		}
4009
4010
		/**
4011
		 * Check if sync was executed in the last $period of seconds.
4012
		 *
4013
		 * @author Vova Feldman (@svovaf)
4014
		 * @since  1.1.7.3
4015
		 *
4016
		 * @param int $period In seconds
4017
		 *
4018
		 * @return bool
4019
		 */
4020
		private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) {
4021
			if ( ! isset( $this->_storage->sync_timestamp ) ) {
4022
				return false;
4023
			}
4024
4025
			return ( $this->_storage->sync_timestamp > ( WP_FS__SCRIPT_START_TIME - $period ) );
4026
		}
4027
4028
		/**
4029
		 * @author Vova Feldman (@svovaf)
4030
		 * @since  1.1.7.3
4031
		 *
4032
		 * @return bool
4033
		 */
4034
		private function is_sync_cron_on() {
4035
			/**
4036
			 * @var object $sync_cron_data
4037
			 */
4038
			$sync_cron_data = $this->_storage->get( 'sync_cron', null );
4039
4040
			return ( ! is_null( $sync_cron_data ) && true === $sync_cron_data->on );
4041
		}
4042
4043
		/**
4044
		 * @author Vova Feldman (@svovaf)
4045
		 * @since  1.1.7.3
4046
		 *
4047
		 * @param int  $start_at        Defaults to now.
4048
		 * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise,
4049
		 *                              schedule job to start right away.
4050
		 */
4051
		private function schedule_sync_cron( $start_at = WP_FS__SCRIPT_START_TIME, $randomize_start = true ) {
4052
			$this->_logger->entrance();
4053
4054
			if ( $randomize_start ) {
4055
				// Schedule first sync with a random 12 hour time range from now.
4056
				$start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) );
4057
			}
4058
4059
			// Schedule daily WP cron.
4060
			wp_schedule_event(
4061
				$start_at,
4062
				'daily',
4063
				$this->get_action_tag( 'data_sync' )
4064
			);
4065
4066
			$this->_storage->store( 'sync_cron', (object) array(
4067
				'version'     => $this->get_plugin_version(),
4068
				'sdk_version' => $this->version,
4069
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
4070
				'on'          => true,
4071
			) );
4072
		}
4073
4074
		/**
4075
		 * Add the actual sync function to the cron job hook.
4076
		 *
4077
		 * @author Vova Feldman (@svovaf)
4078
		 * @since  1.1.7.3
4079
		 */
4080
		private function hook_callback_to_sync_cron() {
4081
			$this->add_action( 'data_sync', array( &$this, '_sync_cron' ) );
4082
		}
4083
4084
		/**
4085
		 * @author Vova Feldman (@svovaf)
4086
		 * @since  1.1.7.3
4087
		 */
4088
		private function clear_sync_cron() {
4089
			$this->_logger->entrance();
4090
4091
			if ( ! $this->is_sync_cron_on() ) {
4092
				return;
4093
			}
4094
4095
			$this->_storage->remove( 'sync_cron' );
4096
4097
			wp_clear_scheduled_hook( $this->get_action_tag( 'data_sync' ) );
4098
		}
4099
4100
		/**
4101
		 * Unix timestamp for next sync cron execution or false if not scheduled.
4102
		 *
4103
		 * @author Vova Feldman (@svovaf)
4104
		 * @since  1.1.7.3
4105
		 *
4106
		 * @return int|false
4107
		 */
4108
		function next_sync_cron() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4109
			$this->_logger->entrance();
4110
4111
			if ( ! $this->is_sync_cron_on() ) {
4112
				return false;
4113
			}
4114
4115
			return wp_next_scheduled( $this->get_action_tag( 'data_sync' ) );
4116
		}
4117
4118
		/**
4119
		 * Unix timestamp for previous sync cron execution or false if never executed.
4120
		 *
4121
		 * @author Vova Feldman (@svovaf)
4122
		 * @since  1.1.7.3
4123
		 *
4124
		 * @return int|false
4125
		 */
4126
		function last_sync_cron() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4127
			$this->_logger->entrance();
4128
4129
			return $this->_storage->get( 'sync_timestamp' );
4130
		}
4131
4132
		#endregion Daily Sync Cron ------------------------------------------------------------------
4133
4134
		#----------------------------------------------------------------------------------
4135
		#region Async Install Sync
4136
		#----------------------------------------------------------------------------------
4137
4138
		/**
4139
		 * @author Vova Feldman (@svovaf)
4140
		 * @since  1.1.7.3
4141
		 *
4142
		 * @return bool
4143
		 */
4144
		private function is_install_sync_scheduled() {
4145
			/**
4146
			 * @var object $cron_data
4147
			 */
4148
			$cron_data = $this->_storage->get( 'install_sync_cron', null );
4149
4150
			return ( ! is_null( $cron_data ) && true === $cron_data->on );
4151
		}
4152
4153
		/**
4154
		 * Instead of running blocking install sync event, execute non blocking scheduled wp-cron.
4155
		 *
4156
		 * @author Vova Feldman (@svovaf)
4157
		 * @since  1.1.7.3
4158
		 */
4159
		private function schedule_install_sync() {
4160
			$this->_logger->entrance();
4161
4162
			$this->clear_install_sync_cron();
4163
4164
			// Schedule immediate install sync.
4165
			wp_schedule_single_event(
4166
				WP_FS__SCRIPT_START_TIME,
4167
				$this->get_action_tag( 'install_sync' )
4168
			);
4169
4170
			$this->_storage->store( 'install_sync_cron', (object) array(
4171
				'version'     => $this->get_plugin_version(),
4172
				'sdk_version' => $this->version,
4173
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
4174
				'on'          => true,
4175
			) );
4176
		}
4177
4178
		/**
4179
		 * Unix timestamp for previous install sync cron execution or false if never executed.
4180
		 *
4181
		 * @todo   There's some very strange bug that $this->_storage->install_sync_timestamp value is not being
4182
		 *         updated. But for sure the sync event is working.
4183
		 *
4184
		 * @author Vova Feldman (@svovaf)
4185
		 * @since  1.1.7.3
4186
		 *
4187
		 * @return int|false
4188
		 */
4189
		function last_install_sync() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4190
			$this->_logger->entrance();
4191
4192
			return $this->_storage->get( 'install_sync_timestamp' );
4193
		}
4194
4195
		/**
4196
		 * Unix timestamp for next install sync cron execution or false if not scheduled.
4197
		 *
4198
		 * @author Vova Feldman (@svovaf)
4199
		 * @since  1.1.7.3
4200
		 *
4201
		 * @return int|false
4202
		 */
4203
		function next_install_sync() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4204
			$this->_logger->entrance();
4205
4206
			if ( ! $this->is_install_sync_scheduled() ) {
4207
				return false;
4208
			}
4209
4210
			return wp_next_scheduled( $this->get_action_tag( 'install_sync' ) );
4211
		}
4212
4213
		/**
4214
		 * Add the actual install sync function to the cron job hook.
4215
		 *
4216
		 * @author Vova Feldman (@svovaf)
4217
		 * @since  1.1.7.3
4218
		 */
4219
		private function hook_callback_to_install_sync() {
4220
			$this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) );
4221
		}
4222
4223
		/**
4224
		 * @author Vova Feldman (@svovaf)
4225
		 * @since  1.1.7.3
4226
		 */
4227
		private function clear_install_sync_cron() {
4228
			$this->_logger->entrance();
4229
4230
			if ( ! $this->is_install_sync_scheduled() ) {
4231
				return;
4232
			}
4233
4234
			$this->_storage->remove( 'install_sync_cron' );
4235
4236
			wp_clear_scheduled_hook( $this->get_action_tag( 'install_sync' ) );
4237
		}
4238
4239
		/**
4240
		 * @author Vova Feldman (@svovaf)
4241
		 * @since  1.1.7.3
4242
		 */
4243
		public function _run_sync_install() {
4244
			$this->_logger->entrance();
4245
4246
			// Update last install sync timestamp.
4247
			$this->_storage->install_sync_timestamp = time();
4248
4249
			$this->sync_install( array(), true );
4250
		}
4251
4252
		#endregion Async Install Sync ------------------------------------------------------------------
4253
4254
		/**
4255
		 * Show a notice that activation is currently pending.
4256
		 *
4257
		 * @author Vova Feldman (@svovaf)
4258
		 * @since  1.0.7
4259
		 *
4260
		 * @param bool|string $email
4261
		 * @param bool        $is_pending_trial Since 1.2.1.5
4262
		 */
4263
		function _add_pending_activation_notice( $email = false, $is_pending_trial = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4264
			if ( ! is_string( $email ) ) {
4265
				$current_user = self::_get_current_wp_user();
4266
				$email        = $current_user->user_email;
4267
			}
4268
4269
			$this->_admin_notices->add_sticky(
4270
				sprintf(
4271
					$this->get_text( 'pending-activation-message' ),
4272
					'<b>' . $this->get_plugin_name() . '</b>',
4273
					'<b>' . $email . '</b>',
4274
					$this->get_text( $is_pending_trial ? 'start-the-trial' : 'complete-the-install' )
4275
				),
4276
				'activation_pending',
4277
				'Thanks!'
4278
			);
4279
		}
4280
4281
		/**
4282
		 * Check if currently in plugin activation.
4283
		 *
4284
		 * @author Vova Feldman (@svovaf)
4285
		 * @since  1.1.4
4286
		 *
4287
		 * @return bool
4288
		 */
4289
		function is_plugin_activation() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4290
			return get_option( 'fs_'
4291
			                   . ( $this->is_plugin() ? '' : $this->_module_type . '_' )
4292
			                   . "{$this->_slug}_activated", false );
4293
		}
4294
4295
		/**
4296
		 *
4297
		 * NOTE: admin_menu action executed before admin_init.
4298
		 *
4299
		 * @author Vova Feldman (@svovaf)
4300
		 * @since  1.0.7
4301
		 */
4302
		function _admin_init_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
Coding Style introduced by
_admin_init_action uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
4303
			/**
4304
			 * Automatically redirect to connect/activation page after plugin activation.
4305
			 *
4306
			 * @since 1.1.7 Do NOT redirect to opt-in when running in network admin mode.
4307
			 */
4308
			if ( $this->is_plugin_activation() ) {
4309
				delete_option( 'fs_'
4310
				               . ( $this->is_plugin() ? '' : $this->_module_type . '_' )
4311
				               . "{$this->_slug}_activated" );
4312
4313
				if ( ! function_exists( 'is_network_admin' ) || ! is_network_admin() ) {
4314
					$this->_redirect_on_activation_hook();
4315
4316
					return;
4317
				}
4318
			}
4319
4320
			if ( fs_request_is_action( $this->get_unique_affix() . '_skip_activation' ) ) {
4321
				check_admin_referer( $this->get_unique_affix() . '_skip_activation' );
4322
4323
				$this->skip_connection();
4324
4325
				fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) );
4326
			}
4327
4328
			if ( ! $this->is_addon() && ! $this->is_registered() && ! $this->is_anonymous() ) {
4329
				if ( ! $this->is_pending_activation() ) {
4330
					if ( ! $this->_menu->is_main_settings_page() ) {
4331
						/**
4332
						 * If a user visits any other admin page before activating the premium-only theme with a valid
4333
						 * license, reactivate the previous theme.
4334
						 *
4335
						 * @author Leo Fajardo (@leorw)
4336
						 * @since  1.2.2
4337
						 */
4338
						if ( $this->is_theme()
4339
						     && $this->is_only_premium()
4340
						     && ! $this->has_settings_menu()
4341
						     && ! isset( $_REQUEST['fs_action'] )
4342
						     && $this->can_activate_previous_theme()
4343
						) {
4344
							$this->activate_previous_theme();
4345
4346
							return;
4347
						}
4348
4349
						if ( $this->is_plugin_new_install() || $this->is_only_premium() ) {
4350
							// Show notice for new plugin installations.
4351
							$this->_admin_notices->add(
4352
								sprintf(
4353
									$this->get_text( 'you-are-step-away' ),
4354
									sprintf( '<b><a href="%s">%s</a></b>',
4355
										$this->get_activation_url(),
4356
										sprintf( $this->get_text( 'activate-x-now' ), $this->get_plugin_name() )
4357
									)
4358
								),
4359
								'',
4360
								'update-nag'
4361
							);
4362
						} else {
4363
							if ( ! isset( $this->_storage->sticky_optin_added ) ) {
4364
								$this->_storage->sticky_optin_added = true;
4365
4366
								// Show notice for new plugin installations.
4367
								$this->_admin_notices->add_sticky(
4368
									sprintf(
4369
										$this->get_text( 'few-plugin-tweaks' ),
4370
										$this->_module_type,
4371
										sprintf( '<b><a href="%s">%s</a></b>',
4372
											$this->get_activation_url(),
4373
											sprintf( $this->get_text( 'optin-x-now' ), $this->get_plugin_name() )
4374
										)
4375
									),
4376
									'connect_account',
4377
									'',
4378
									'update-nag'
4379
								);
4380
							}
4381
4382
							if ( $this->has_filter( 'optin_pointer_element' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->has_filter('optin_pointer_element') of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
4383
								// Don't show admin nag if plugin update.
4384
								wp_enqueue_script( 'wp-pointer' );
4385
								wp_enqueue_style( 'wp-pointer' );
4386
4387
								$this->_enqueue_connect_essentials();
4388
4389
								add_action( 'admin_print_footer_scripts', array(
4390
									$this,
4391
									'_add_connect_pointer_script'
4392
								) );
4393
							}
4394
						}
4395
					}
4396
				}
4397
4398
				if ( $this->is_theme() &&
4399
				     $this->_menu->is_main_settings_page()
4400
				) {
4401
					$this->_show_theme_activation_optin_dialog();
4402
				}
4403
			}
4404
4405
			$this->_add_upgrade_action_link();
4406
		}
4407
4408
		/**
4409
		 * Enqueue connect requires scripts and styles.
4410
		 *
4411
		 * @author Vova Feldman (@svovaf)
4412
		 * @since  1.1.4
4413
		 */
4414
		function _enqueue_connect_essentials() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4415
			wp_enqueue_script( 'jquery' );
4416
			wp_enqueue_script( 'json2' );
4417
4418
			fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' );
4419
			fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' );
4420
4421
			fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' );
4422
		}
4423
4424
		/**
4425
		 * Add connect / opt-in pointer.
4426
		 *
4427
		 * @author Vova Feldman (@svovaf)
4428
		 * @since  1.1.4
4429
		 */
4430
		function _add_connect_pointer_script() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4431
			$vars            = array( 'id' => $this->_module_id );
4432
			$pointer_content = fs_get_template( 'connect.php', $vars );
4433
			?>
4434
			<script type="text/javascript">// <![CDATA[
4435
				jQuery(document).ready(function ($) {
4436
					if ('undefined' !== typeof(jQuery().pointer)) {
4437
4438
						var element = <?php echo $this->apply_filters( 'optin_pointer_element', '$("#non_existing_element");' ) ?>;
4439
4440
						if (element.length > 0) {
4441
							var optin = $(element).pointer($.extend(true, {}, {
4442
								content     : <?php echo json_encode( $pointer_content ) ?>,
4443
								position    : {
4444
									edge : 'left',
4445
									align: 'center'
4446
								},
4447
								buttons     : function () {
4448
									// Don't show pointer buttons.
4449
									return '';
4450
								},
4451
								pointerWidth: 482
4452
							}, <?php echo $this->apply_filters( 'optin_pointer_options_json', '{}' ) ?>));
4453
4454
							<?php
4455
							echo $this->apply_filters( 'optin_pointer_execute', "
4456
4457
							optin.pointer('open');
4458
4459
							// Tag the opt-in pointer with custom class.
4460
							$('.wp-pointer #fs_connect')
4461
								.parents('.wp-pointer.wp-pointer-top')
4462
								.addClass('fs-opt-in-pointer');
4463
4464
							", 'element', 'optin' ) ?>
4465
						}
4466
					}
4467
				});
4468
				// ]]></script>
4469
			<?php
4470
		}
4471
4472
		/**
4473
		 * Return current page's URL.
4474
		 *
4475
		 * @author Vova Feldman (@svovaf)
4476
		 * @since  1.0.7
4477
		 *
4478
		 * @return string
4479
		 */
4480
		function current_page_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
Coding Style introduced by
current_page_url uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
4481
			$url = 'http';
4482
4483
			if ( isset( $_SERVER["HTTPS"] ) ) {
4484
				if ( $_SERVER["HTTPS"] == "on" ) {
4485
					$url .= "s";
4486
				}
4487
			}
4488
			$url .= "://";
4489
			if ( $_SERVER["SERVER_PORT"] != "80" ) {
4490
				$url .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
4491
			} else {
4492
				$url .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
4493
			}
4494
4495
			return esc_url( $url );
4496
		}
4497
4498
		/**
4499
		 * Check if the current page is the plugin's main admin settings page.
4500
		 *
4501
		 * @author Vova Feldman (@svovaf)
4502
		 * @since  1.0.7
4503
		 *
4504
		 * @return bool
4505
		 */
4506
		function _is_plugin_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4507
			return fs_is_plugin_page( $this->_menu->get_raw_slug() ) ||
4508
			       fs_is_plugin_page( $this->_slug );
4509
		}
4510
4511
		/* Events
4512
		------------------------------------------------------------------------------------------------------------------*/
4513
		/**
4514
		 * Delete site install from Database.
4515
		 *
4516
		 * @author Vova Feldman (@svovaf)
4517
		 * @since  1.0.1
4518
		 *
4519
		 * @param bool $store
4520
		 */
4521
		function _delete_site( $store = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4522
			self::_delete_site_by_slug( $this->_slug, $this->_module_type, $store );
4523
		}
4524
4525
		/**
4526
		 * Delete site install from Database.
4527
		 *
4528
		 * @author Vova Feldman (@svovaf)
4529
		 * @since  1.2.2.7
4530
		 *
4531
		 * @param string $slug
4532
		 * @param string $module_type
4533
		 * @param bool   $store
4534
		 */
4535
		static function _delete_site_by_slug($slug, $module_type, $store = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4536
			$sites = self::get_all_sites( $module_type );
4537
4538
			if ( isset( $sites[ $slug ] ) ) {
4539
				unset( $sites[ $slug ] );
4540
			}
4541
4542
			self::set_account_option_by_module( $module_type, 'sites', $sites, $store );
4543
		}
4544
4545
		/**
4546
		 * Delete plugin's plans information.
4547
		 *
4548
		 * @param bool $store Flush to Database if true.
4549
		 *
4550
		 * @author Vova Feldman (@svovaf)
4551
		 * @since  1.0.9
4552
		 */
4553
		private function _delete_plans( $store = true ) {
4554
			$this->_logger->entrance();
4555
4556
			$plans = self::get_all_plans( $this->_module_type );
4557
4558
			unset( $plans[ $this->_slug ] );
4559
4560
			$this->set_account_option( 'plans', $plans, $store );
4561
		}
4562
4563
		/**
4564
		 * Delete all plugin licenses.
4565
		 *
4566
		 * @author Vova Feldman (@svovaf)
4567
		 * @since  1.0.9
4568
		 *
4569
		 * @param bool        $store
4570
		 * @param string|bool $plugin_slug
4571
		 */
4572
		private function _delete_licenses( $store = true, $plugin_slug = false ) {
4573
			$this->_logger->entrance();
4574
4575
			$all_licenses = self::get_all_licenses( $this->_module_type );
4576
4577
			if ( ! is_string( $plugin_slug ) ) {
4578
				$plugin_slug = $this->_slug;
4579
			}
4580
4581
			unset( $all_licenses[ $plugin_slug ] );
4582
4583
			$this->set_account_option( 'licenses', $all_licenses, $store );
4584
		}
4585
4586
		/**
4587
		 * Check if Freemius was added on new plugin installation.
4588
		 *
4589
		 * @author Vova Feldman (@svovaf)
4590
		 * @since  1.1.5
4591
		 *
4592
		 * @return bool
4593
		 */
4594
		function is_plugin_new_install() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4595
			return isset( $this->_storage->is_plugin_new_install ) &&
4596
			       $this->_storage->is_plugin_new_install;
4597
		}
4598
4599
		/**
4600
		 * Check if it's the first plugin release that is running Freemius.
4601
		 *
4602
		 * @author Vova Feldman (@svovaf)
4603
		 * @since  1.2.1.5
4604
		 *
4605
		 * @return bool
4606
		 */
4607
		function is_first_freemius_powered_version() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4608
			return empty( $this->_storage->plugin_last_version );
4609
		}
4610
4611
		/**
4612
		 * @author Leo Fajardo (@leorw)
4613
		 * @since  1.2.2
4614
		 *
4615
		 * @return bool|string
4616
		 */
4617
		private function get_previous_theme_slug() {
4618
			return isset( $this->_storage->previous_theme ) ?
4619
				$this->_storage->previous_theme :
0 ignored issues
show
Documentation introduced by
The property previous_theme does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
4620
				false;
4621
		}
4622
4623
		/**
4624
		 * @author Leo Fajardo (@leorw)
4625
		 * @since  1.2.2
4626
		 *
4627
		 * @return string
4628
		 */
4629
		private function can_activate_previous_theme() {
4630
			$slug = $this->get_previous_theme_slug();
4631
			if ( false !== $slug && current_user_can( 'switch_themes' ) ) {
4632
				$theme_instance = wp_get_theme( $slug );
4633
4634
				return $theme_instance->exists();
4635
			}
4636
4637
			return false;
4638
		}
4639
4640
		/**
4641
		 * @author Leo Fajardo (@leorw)
4642
		 * @since  1.2.2
4643
		 *
4644
		 * @return string
4645
		 */
4646
		private function activate_previous_theme() {
4647
			switch_theme( $this->get_previous_theme_slug() );
4648
			unset( $this->_storage->previous_theme );
4649
4650
			global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4651
			if ( 'themes.php' === $pagenow ) {
4652
				/**
4653
				 * Refresh the active theme information.
4654
				 *
4655
				 * @author Leo Fajardo (@leorw)
4656
				 * @since  1.2.2
4657
				 */
4658
				fs_redirect( admin_url( $pagenow ) );
4659
			}
4660
		}
4661
4662
		/**
4663
		 * @author Leo Fajardo (@leorw)
4664
		 * @since  1.2.2
4665
		 *
4666
		 * @return string
4667
		 */
4668
		function get_previous_theme_activation_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4669
			if ( ! $this->can_activate_previous_theme() ) {
4670
				return '';
4671
			}
4672
4673
			/**
4674
			 * Activation URL
4675
			 *
4676
			 * @author Leo Fajardo (@leorw)
4677
			 * @since  1.2.2
4678
			 */
4679
			return wp_nonce_url(
4680
				admin_url( 'themes.php?action=activate&stylesheet=' . urlencode( $this->get_previous_theme_slug() ) ),
4681
				'switch-theme_' . $this->get_previous_theme_slug()
4682
			);
4683
		}
4684
4685
		/**
4686
		 * Saves the slug of the previous theme if it still exists so that it can be used by the logic in the opt-in
4687
		 * form that decides whether to add a close button to the opt-in dialog or not. So after a premium-only theme is
4688
		 * activated, the close button will appear and will reactivate the previous theme if clicked. If the previous
4689
		 * theme doesn't exist, then there will be no close button.
4690
		 *
4691
		 * @author Leo Fajardo (@leorw)
4692
		 * @since  1.2.2
4693
		 *
4694
		 * @param  string        $slug_or_name Old theme's slug or name.
4695
		 * @param  bool|WP_Theme $old_theme    WP_Theme instance of the old theme if it still exists.
4696
		 */
4697
		function _activate_theme_event_hook( $slug_or_name, $old_theme = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4698
			$this->_storage->previous_theme = ( false !== $old_theme ) ?
0 ignored issues
show
Documentation introduced by
The property previous_theme does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
4699
				$old_theme->get_stylesheet() :
4700
				$slug_or_name;
4701
4702
			$this->_activate_plugin_event_hook();
4703
		}
4704
4705
		/**
4706
		 * Plugin activated hook.
4707
		 *
4708
		 * @author Vova Feldman (@svovaf)
4709
		 * @since  1.0.1
4710
		 *
4711
		 * @uses   FS_Api
4712
		 */
4713
		function _activate_plugin_event_hook() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4714
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4715
4716
			if ( ! $this->is_user_admin() ) {
4717
				return;
4718
			}
4719
4720
			$this->unregister_uninstall_hook();
4721
4722
			// Clear API cache on activation.
4723
			FS_Api::clear_cache();
4724
4725
			$is_premium_version_activation = ( current_filter() !== ( 'activate_' . $this->_free_plugin_basename ) );
4726
4727
			$this->_logger->info( 'Activating ' . ( $is_premium_version_activation ? 'premium' : 'free' ) . ' plugin version.' );
4728
4729
			// 1. If running in the activation of the FREE module, get the basename of the PREMIUM.
4730
			// 2. If running in the activation of the PREMIUM module, get the basename of the FREE.
4731
			$other_version_basename = $is_premium_version_activation ?
4732
				$this->_free_plugin_basename :
4733
				$this->premium_plugin_basename();
4734
4735
			/**
4736
			 * If the other module version is activate, deactivate it.
4737
			 *
4738
			 * @author Leo Fajardo (@leorw)
4739
			 * @since  1.2.2
4740
			 */
4741
			if ( is_plugin_active( $other_version_basename ) ) {
4742
				deactivate_plugins( $other_version_basename );
4743
			}
4744
4745
			if ( $this->is_registered() ) {
4746
				if ( $is_premium_version_activation ) {
4747
					$this->reconnect_locally();
4748
				}
4749
4750
4751
				// Schedule re-activation event and sync.
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
4752
//				$this->sync_install( array(), true );
4753
				$this->schedule_install_sync();
4754
4755
				// If activating the premium module version, add an admin notice to congratulate for an upgrade completion.
4756
				if ( $is_premium_version_activation ) {
4757
					$this->_admin_notices->add(
4758
						sprintf( $this->get_text( 'successful-version-upgrade-message' ), sprintf( '<b>%s</b>', $this->_plugin->title ) ),
4759
						$this->get_text( 'woot' ) . '!'
4760
					);
4761
				}
4762
			} else if ( $this->is_anonymous() ) {
4763
				/**
4764
				 * Reset "skipped" click cache on the following:
4765
				 *  1. Freemius DEV mode.
4766
				 *  2. WordPress DEBUG mode.
4767
				 *  3. If a plugin and the user skipped the exact same version before.
4768
				 *
4769
				 * @since 1.2.2.7 Ulrich Pogson (@grapplerulrich) asked to not reset the SKIPPED flag if the exact same THEME version was activated before unless the developer is running with WP_DEBUG on, or Freemius debug mode on (WP_FS__DEV_MODE).
4770
				 *
4771
				 * @todo 4. If explicitly asked to retry after every activation.
4772
				 */
4773
				if ( WP_FS__DEV_MODE ||
4774
				     (
4775
				     	( $this->is_plugin() || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) &&
4776
				        $this->get_plugin_version() == $this->_storage->is_anonymous['version']
4777
				     )
4778
				) {
4779
					$this->reset_anonymous_mode();
4780
				}
4781
			}
4782
4783
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
4784
				/**
4785
				 * If no previous version of plugin's version exist, it means that it's either
4786
				 * the first time that the plugin installed on the site, or the plugin was installed
4787
				 * before but didn't have Freemius integrated.
4788
				 *
4789
				 * Since register_activation_hook() do NOT fires on updates since 3.1, and only fires
4790
				 * on manual activation via the dashboard, is_plugin_activation() is TRUE
4791
				 * only after immediate activation.
4792
				 *
4793
				 * @since 1.1.4
4794
				 * @link  https://make.wordpress.org/core/2010/10/27/plugin-activation-hooks-no-longer-fire-for-updates/
4795
				 */
4796
				$this->_storage->is_plugin_new_install = empty( $this->_storage->plugin_last_version );
4797
			}
4798
4799
			if ( ! $this->_anonymous_mode &&
4800
			     $this->has_api_connectivity( WP_FS__DEV_MODE ) &&
4801
			     ! $this->_isAutoInstall
4802
			) {
4803
				// Store hint that the plugin was just activated to enable auto-redirection to settings.
4804
				add_option( 'fs_'
4805
				            . ( $this->is_plugin() ? '' : $this->_module_type . '_' )
4806
				            . "{$this->_slug}_activated", true );
4807
			}
4808
4809
			/**
4810
			 * Activation hook is executed after the plugin's main file is loaded, therefore,
4811
			 * after the plugin was loaded. The logic is located at activate_plugin()
4812
			 * ./wp-admin/includes/plugin.php.
4813
			 *
4814
			 * @author Vova Feldman (@svovaf)
4815
			 * @since  1.1.9
4816
			 */
4817
			$this->_storage->was_plugin_loaded = true;
4818
		}
4819
4820
		/**
4821
		 * Delete account.
4822
		 *
4823
		 * @author Vova Feldman (@svovaf)
4824
		 * @since  1.0.3
4825
		 *
4826
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
4827
		 */
4828
		function delete_account_event( $check_user = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4829
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4830
4831
			if ( $check_user && ! $this->is_user_admin() ) {
4832
				return;
4833
			}
4834
4835
			$this->do_action( 'before_account_delete' );
4836
4837
			// Clear all admin notices.
4838
			$this->_admin_notices->clear_all_sticky();
4839
4840
			$this->_delete_site( false );
4841
4842
			$this->_delete_plans( false );
4843
4844
			$this->_delete_licenses( false );
4845
4846
			// Delete add-ons related to plugin's account.
4847
			$this->_delete_account_addons( false );
4848
4849
			// @todo Delete plans and licenses of add-ons.
4850
4851
			self::$_accounts->store();
4852
4853
			/**
4854
			 * IMPORTANT:
4855
			 *  Clear crons must be executed before clearing all storage.
4856
			 *  Otherwise, the cron will not be cleared.
4857
			 */
4858
			$this->clear_sync_cron();
4859
			$this->clear_install_sync_cron();
4860
4861
			// Clear all storage data.
4862
			$this->_storage->clear_all( true, array(
4863
				'connectivity_test',
4864
				'is_on',
4865
			) );
4866
4867
			// Send delete event.
4868
			$this->get_api_site_scope()->call( '/', 'delete' );
4869
4870
			$this->do_action( 'after_account_delete' );
4871
		}
4872
4873
		/**
4874
		 * Plugin deactivation hook.
4875
		 *
4876
		 * @author Vova Feldman (@svovaf)
4877
		 * @since  1.0.1
4878
		 */
4879
		function _deactivate_plugin_hook() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4880
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4881
4882
			if ( ! current_user_can( 'activate_plugins' ) ) {
4883
				return;
4884
			}
4885
4886
			$this->_admin_notices->clear_all_sticky();
4887
			if ( isset( $this->_storage->sticky_optin_added ) ) {
4888
				unset( $this->_storage->sticky_optin_added );
4889
			}
4890
4891
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
4892
				// Remember that plugin was already installed.
4893
				$this->_storage->is_plugin_new_install = false;
4894
			}
4895
4896
			// Hook to plugin uninstall.
4897
			register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) );
4898
4899
			$this->clear_module_main_file_cache();
4900
			$this->clear_sync_cron();
4901
			$this->clear_install_sync_cron();
4902
4903
			if ( $this->is_registered() ) {
4904
				// Send deactivation event.
4905
				$this->sync_install( array(
4906
					'is_active' => false,
4907
				) );
4908
			} else {
4909
				if ( ! $this->has_api_connectivity() ) {
4910
					// Reset connectivity test cache.
4911
					unset( $this->_storage->connectivity_test );
4912
				}
4913
			}
4914
4915
			// Clear API cache on deactivation.
4916
			FS_Api::clear_cache();
4917
4918
			$this->remove_sdk_reference();
4919
		}
4920
4921
		/**
4922
		 * @author Vova Feldman (@svovaf)
4923
		 * @since  1.1.6
4924
		 */
4925
		private function remove_sdk_reference() {
4926
			global $fs_active_plugins;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4927
4928
			foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) {
4929
				if ( $this->_plugin_basename == $data->plugin_path ) {
4930
					unset( $fs_active_plugins->plugins[ $sdk_path ] );
4931
					break;
4932
				}
4933
			}
4934
4935
			fs_fallback_to_newest_active_sdk();
4936
		}
4937
4938
		/**
4939
		 * @author Vova Feldman (@svovaf)
4940
		 * @since  1.1.3
4941
		 *
4942
		 * @param bool $is_anonymous
4943
		 */
4944
		private function set_anonymous_mode( $is_anonymous = true ) {
4945
			// Store information regarding skip to try and opt-in the user
4946
			// again in the future.
4947
			$this->_storage->is_anonymous = array(
4948
				'is'        => $is_anonymous,
4949
				'timestamp' => WP_FS__SCRIPT_START_TIME,
4950
				'version'   => $this->get_plugin_version(),
4951
			);
4952
4953
			// Update anonymous mode cache.
4954
			$this->_is_anonymous = $is_anonymous;
4955
		}
4956
4957
		/**
4958
		 * @author Vova Feldman (@svovaf)
4959
		 * @since  1.1.3
4960
		 */
4961
		private function reset_anonymous_mode() {
4962
			unset( $this->_storage->is_anonymous );
4963
4964
			/**
4965
			 * Ensure that this field is also "false", otherwise, if the current module's type is "theme" and the module
4966
			 * has no menus, the opt-in popup will not be shown immediately (in this case, the user will have to click
4967
			 * on the admin notice that contains the opt-in link in order to trigger the opt-in popup).
4968
			 *
4969
			 * @author Leo Fajardo (@leorw)
4970
			 * @since  1.2.2
4971
			 */
4972
			unset( $this->_is_anonymous );
4973
		}
4974
4975
		/**
4976
		 * Clears the anonymous mode and redirects to the opt-in screen.
4977
		 *
4978
		 * @author Vova Feldman (@svovaf)
4979
		 * @since  1.1.7
4980
		 */
4981
		function connect_again() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4982
			if ( ! $this->is_anonymous() ) {
4983
				return;
4984
			}
4985
4986
			$this->reset_anonymous_mode();
4987
4988
			fs_redirect( $this->get_activation_url() );
4989
		}
4990
4991
		/**
4992
		 * Skip account connect, and set anonymous mode.
4993
		 *
4994
		 * @author Vova Feldman (@svovaf)
4995
		 * @since  1.1.1
4996
		 */
4997
		private function skip_connection() {
4998
			$this->_logger->entrance();
4999
5000
			$this->_admin_notices->remove_sticky( 'connect_account' );
5001
5002
			$this->set_anonymous_mode();
5003
5004
			// Send anonymous skip event.
5005
			// No user identified info nor any tracking will be sent after the user skips the opt-in.
5006
			$this->get_api_plugin_scope()->call( 'skip.json', 'put', array(
5007
				'uid' => $this->get_anonymous_id(),
5008
			) );
5009
		}
5010
5011
		/**
5012
		 * Plugin version update hook.
5013
		 *
5014
		 * @author Vova Feldman (@svovaf)
5015
		 * @since  1.0.4
5016
		 */
5017
		private function update_plugin_version_event() {
5018
			$this->_logger->entrance();
5019
5020
			if ( ! $this->is_registered() ) {
5021
				return;
5022
			}
5023
5024
			$this->schedule_install_sync();
5025
//			$this->sync_install( array(), true );
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
5026
		}
5027
5028
		/**
5029
		 * Return a list of modified plugins since the last sync.
5030
		 *
5031
		 * Note:
5032
		 *  There's no point to store a plugins counter since even if the number of
5033
		 *  plugins didn't change, we still need to check if the versions are all the
5034
		 *  same and the activity state is similar.
5035
		 *
5036
		 * @author Vova Feldman (@svovaf)
5037
		 * @since  1.1.8
5038
		 *
5039
		 * @return array|false
5040
		 */
5041
		private function get_plugins_data_for_api() {
5042
			// Alias.
5043
			$option_name = 'all_plugins';
5044
5045
			$all_cached_plugins = self::$_accounts->get_option( $option_name );
5046
5047
			if ( ! is_object( $all_cached_plugins ) ) {
5048
				$all_cached_plugins = (object) array(
5049
					'timestamp' => '',
5050
					'md5'       => '',
5051
					'plugins'   => array(),
5052
				);
5053
			}
5054
5055
			$time = time();
5056
5057
			if ( ! empty( $all_cached_plugins->timestamp ) &&
5058
			     ( $time - $all_cached_plugins->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC
5059
			) {
5060
				// Don't send plugin updates if last update was in the past 5 min.
5061
				return false;
5062
			}
5063
5064
			// Write timestamp to lock the logic.
5065
			$all_cached_plugins->timestamp = $time;
5066
			self::$_accounts->set_option( $option_name, $all_cached_plugins, true );
5067
5068
			// Reload options from DB.
5069
			self::$_accounts->load( true );
5070
			$all_cached_plugins = self::$_accounts->get_option( $option_name );
5071
5072
			if ( $time != $all_cached_plugins->timestamp ) {
5073
				// If timestamp is different, then another thread captured the lock.
5074
				return false;
5075
			}
5076
5077
			// Check if there's a change in plugins.
5078
			$all_plugins = self::get_all_plugins();
5079
5080
			// Check if plugins changed.
5081
			ksort( $all_plugins );
5082
5083
			$plugins_signature = '';
5084
			foreach ( $all_plugins as $basename => $data ) {
5085
				$plugins_signature .= $data['slug'] . ',' .
5086
				                      $data['Version'] . ',' .
5087
				                      ( $data['is_active'] ? '1' : '0' ) . ';';
5088
			}
5089
5090
			// Check if plugins status changed (version or active/inactive).
5091
			$plugins_changed = ( $all_cached_plugins->md5 !== md5( $plugins_signature ) );
5092
5093
			$plugins_update_data = array();
5094
5095
			if ( $plugins_changed ) {
5096
				// Change in plugins, report changes.
5097
5098
				// Update existing plugins info.
5099
				foreach ( $all_cached_plugins->plugins as $basename => $data ) {
5100
					if ( ! isset( $all_plugins[ $basename ] ) ) {
5101
						// Plugin uninstalled.
5102
						$uninstalled_plugin_data                   = $data;
5103
						$uninstalled_plugin_data['is_active']      = false;
5104
						$uninstalled_plugin_data['is_uninstalled'] = true;
5105
						$plugins_update_data[]                     = $uninstalled_plugin_data;
5106
5107
						unset( $all_plugins[ $basename ] );
5108
						unset( $all_cached_plugins->plugins[ $basename ] );
5109
					} else if ( $data['is_active'] !== $all_plugins[ $basename ]['is_active'] ||
5110
					            $data['version'] !== $all_plugins[ $basename ]['Version']
5111
					) {
5112
						// Plugin activated or deactivated, or version changed.
5113
						$all_cached_plugins->plugins[ $basename ]['is_active'] = $all_plugins[ $basename ]['is_active'];
5114
						$all_cached_plugins->plugins[ $basename ]['version']   = $all_plugins[ $basename ]['Version'];
5115
5116
						$plugins_update_data[] = $all_cached_plugins->plugins[ $basename ];
5117
					}
5118
				}
5119
5120
				// Find new plugins that weren't yet seen before.
5121
				foreach ( $all_plugins as $basename => $data ) {
5122
					if ( ! isset( $all_cached_plugins->plugins[ $basename ] ) ) {
5123
						// New plugin.
5124
						$new_plugin = array(
5125
							'slug'           => $data['slug'],
5126
							'version'        => $data['Version'],
5127
							'title'          => $data['Name'],
5128
							'is_active'      => $data['is_active'],
5129
							'is_uninstalled' => false,
5130
						);
5131
5132
						$plugins_update_data[]                    = $new_plugin;
5133
						$all_cached_plugins->plugins[ $basename ] = $new_plugin;
5134
					}
5135
				}
5136
5137
				$all_cached_plugins->md5       = md5( $plugins_signature );
5138
				$all_cached_plugins->timestamp = $time;
5139
				self::$_accounts->set_option( $option_name, $all_cached_plugins, true );
5140
			}
5141
5142
			return $plugins_update_data;
5143
		}
5144
5145
		/**
5146
		 * Return a list of modified themes since the last sync.
5147
		 *
5148
		 * Note:
5149
		 *  There's no point to store a themes counter since even if the number of
5150
		 *  themes didn't change, we still need to check if the versions are all the
5151
		 *  same and the activity state is similar.
5152
		 *
5153
		 * @author Vova Feldman (@svovaf)
5154
		 * @since  1.1.8
5155
		 *
5156
		 * @return array|false
5157
		 */
5158
		private function get_themes_data_for_api() {
5159
			// Alias.
5160
			$option_name = 'all_themes';
5161
5162
			$all_cached_themes = self::$_accounts->get_option( $option_name );
5163
5164
			if ( ! is_object( $all_cached_themes ) ) {
5165
				$all_cached_themes = (object) array(
5166
					'timestamp' => '',
5167
					'md5'       => '',
5168
					'themes'    => array(),
5169
				);
5170
			}
5171
5172
			$time = time();
5173
5174
			if ( ! empty( $all_cached_themes->timestamp ) &&
5175
			     ( $time - $all_cached_themes->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC
5176
			) {
5177
				// Don't send theme updates if last update was in the past 5 min.
5178
				return false;
5179
			}
5180
5181
			// Write timestamp to lock the logic.
5182
			$all_cached_themes->timestamp = $time;
5183
			self::$_accounts->set_option( $option_name, $all_cached_themes, true );
5184
5185
			// Reload options from DB.
5186
			self::$_accounts->load( true );
5187
			$all_cached_themes = self::$_accounts->get_option( $option_name );
5188
5189
			if ( $time != $all_cached_themes->timestamp ) {
5190
				// If timestamp is different, then another thread captured the lock.
5191
				return false;
5192
			}
5193
5194
			// Get active theme.
5195
			$active_theme            = wp_get_theme();
5196
			$active_theme_stylesheet = $active_theme->get_stylesheet();
5197
5198
			// Check if there's a change in themes.
5199
			$all_themes = wp_get_themes();
5200
5201
			// Check if themes changed.
5202
			ksort( $all_themes );
5203
5204
			$themes_signature = '';
5205
			foreach ( $all_themes as $slug => $data ) {
5206
				$is_active = ( $slug === $active_theme_stylesheet );
5207
				$themes_signature .= $slug . ',' .
5208
				                     $data->version . ',' .
5209
				                     ( $is_active ? '1' : '0' ) . ';';
5210
			}
5211
5212
			// Check if themes status changed (version or active/inactive).
5213
			$themes_changed = ( $all_cached_themes->md5 !== md5( $themes_signature ) );
5214
5215
			$themes_update_data = array();
5216
5217
			if ( $themes_changed ) {
5218
				// Change in themes, report changes.
5219
5220
				// Update existing themes info.
5221
				foreach ( $all_cached_themes->themes as $slug => $data ) {
5222
					$is_active = ( $slug === $active_theme_stylesheet );
5223
5224
					if ( ! isset( $all_themes[ $slug ] ) ) {
5225
						// Plugin uninstalled.
5226
						$uninstalled_theme_data                   = $data;
5227
						$uninstalled_theme_data['is_active']      = false;
5228
						$uninstalled_theme_data['is_uninstalled'] = true;
5229
						$themes_update_data[]                     = $uninstalled_theme_data;
5230
5231
						unset( $all_themes[ $slug ] );
5232
						unset( $all_cached_themes->themes[ $slug ] );
5233
					} else if ( $data['is_active'] !== $is_active ||
5234
					            $data['version'] !== $all_themes[ $slug ]->version
5235
					) {
5236
						// Plugin activated or deactivated, or version changed.
5237
5238
						$all_cached_themes->themes[ $slug ]['is_active'] = $is_active;
5239
						$all_cached_themes->themes[ $slug ]['version']   = $all_themes[ $slug ]->version;
5240
5241
						$themes_update_data[] = $all_cached_themes->themes[ $slug ];
5242
					}
5243
				}
5244
5245
				// Find new themes that weren't yet seen before.
5246
				foreach ( $all_themes as $slug => $data ) {
5247
					if ( ! isset( $all_cached_themes->themes[ $slug ] ) ) {
5248
						$is_active = ( $slug === $active_theme_stylesheet );
5249
5250
						// New plugin.
5251
						$new_plugin = array(
5252
							'slug'           => $slug,
5253
							'version'        => $data->version,
5254
							'title'          => $data->name,
5255
							'is_active'      => $is_active,
5256
							'is_uninstalled' => false,
5257
						);
5258
5259
						$themes_update_data[]               = $new_plugin;
5260
						$all_cached_themes->themes[ $slug ] = $new_plugin;
5261
					}
5262
				}
5263
5264
				$all_cached_themes->md5       = md5( $themes_signature );
5265
				$all_cached_themes->timestamp = time();
5266
				self::$_accounts->set_option( $option_name, $all_cached_themes, true );
5267
			}
5268
5269
			return $themes_update_data;
5270
		}
5271
5272
		/**
5273
		 * Update install details.
5274
		 *
5275
		 * @author Vova Feldman (@svovaf)
5276
		 * @since  1.1.2
5277
		 *
5278
		 * @param string[] string           $override
5279
		 * @param bool     $include_plugins Since 1.1.8 by default include plugin changes.
5280
		 * @param bool     $include_themes  Since 1.1.8 by default include plugin changes.
5281
		 *
5282
		 * @return array
5283
		 */
5284
		private function get_install_data_for_api(
5285
			array $override,
5286
			$include_plugins = true,
5287
			$include_themes = true
5288
		) {
5289
			/**
5290
			 * @since 1.1.8 Also send plugin updates.
5291
			 */
5292
			if ( $include_plugins && ! isset( $override['plugins'] ) ) {
5293
				$plugins = $this->get_plugins_data_for_api();
5294
				if ( ! empty( $plugins ) ) {
5295
					$override['plugins'] = $plugins;
5296
				}
5297
			}
5298
			/**
5299
			 * @since 1.1.8 Also send themes updates.
5300
			 */
5301
			if ( $include_themes && ! isset( $override['themes'] ) ) {
5302
				$themes = $this->get_themes_data_for_api();
5303
				if ( ! empty( $themes ) ) {
5304
					$override['themes'] = $themes;
5305
				}
5306
			}
5307
5308
			return array_merge( array(
5309
				'version'                      => $this->get_plugin_version(),
5310
				'is_premium'                   => $this->is_premium(),
5311
				'language'                     => get_bloginfo( 'language' ),
5312
				'charset'                      => get_bloginfo( 'charset' ),
5313
				'platform_version'             => get_bloginfo( 'version' ),
5314
				'sdk_version'                  => $this->version,
5315
				'programming_language_version' => phpversion(),
5316
				'title'                        => get_bloginfo( 'name' ),
5317
				'url'                          => get_site_url(),
5318
				// Special params.
5319
				'is_active'                    => true,
5320
				'is_disconnected'              => $this->is_tracking_prohibited(),
5321
				'is_uninstalled'               => false,
5322
			), $override );
5323
		}
5324
5325
		/**
5326
		 * Update install only if changed.
5327
		 *
5328
		 * @author Vova Feldman (@svovaf)
5329
		 * @since  1.0.9
5330
		 *
5331
		 * @param string[] string $override
5332
		 * @param bool     $flush
5333
		 *
5334
		 * @return false|object|string
5335
		 */
5336
		private function send_install_update( $override = array(), $flush = false ) {
5337
			$this->_logger->entrance();
5338
5339
			$check_properties = $this->get_install_data_for_api( $override );
5340
5341
			if ( $flush ) {
5342
				$params = $check_properties;
5343
			} else {
5344
				$params           = array();
5345
				$special          = array();
5346
				$special_override = false;
5347
5348
				foreach ( $check_properties as $p => $v ) {
5349
					if ( property_exists( $this->_site, $p ) ) {
5350
						if ( ( is_bool( $this->_site->{$p} ) || ! empty( $this->_site->{$p} ) ) &&
5351
						     $this->_site->{$p} != $v
5352
						) {
5353
							$this->_site->{$p} = $v;
5354
							$params[ $p ]      = $v;
5355
						}
5356
					} else {
5357
						$special[ $p ] = $v;
5358
5359
						if ( isset( $override[ $p ] ) ||
5360
						     'plugins' === $p ||
5361
						     'themes' === $p
5362
						) {
5363
							$special_override = true;
5364
						}
5365
					}
5366
				}
5367
5368
				if ( $special_override || 0 < count( $params ) ) {
5369
					// Add special params only if has at least one
5370
					// standard param, or if explicitly requested to
5371
					// override a special param or a param which is not exist
5372
					// in the install object.
5373
					$params = array_merge( $params, $special );
5374
				}
5375
			}
5376
5377
			if ( 0 < count( $params ) ) {
5378
				// Update last install sync timestamp.
5379
				$this->_storage->install_sync_timestamp = time();
5380
5381
				$params['uid'] = $this->get_anonymous_id();
5382
5383
				// Send updated values to FS.
5384
				$site = $this->get_api_site_scope()->call( '/', 'put', $params );
5385
5386
				if ( $this->is_api_result_entity( $site ) ) {
5387
					// I successfully sent install update, clear scheduled sync if exist.
5388
					$this->clear_install_sync_cron();
5389
				}
5390
5391
				return $site;
5392
			}
5393
5394
			return false;
5395
		}
5396
5397
		/**
5398
		 * Update install only if changed.
5399
		 *
5400
		 * @author Vova Feldman (@svovaf)
5401
		 * @since  1.0.9
5402
		 *
5403
		 * @param string[] string $override
5404
		 * @param bool     $flush
5405
		 */
5406
		private function sync_install( $override = array(), $flush = false ) {
5407
			$this->_logger->entrance();
5408
5409
			$site = $this->send_install_update( $override, $flush );
5410
5411
			if ( false === $site ) {
5412
				// No sync required.
5413
				return;
5414
			}
5415
5416
			if ( ! $this->is_api_result_entity( $site ) ) {
5417
				// Failed to sync, don't update locally.
5418
				return;
5419
			}
5420
5421
			$plan              = $this->get_plan();
5422
			$this->_site       = new FS_Site( $site );
0 ignored issues
show
Documentation introduced by
$site is of type object|string, but the function expects a object<stdClass>|boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
5423
			$this->_site->plan = $plan;
0 ignored issues
show
Documentation Bug introduced by
It seems like $plan can also be of type false. However, the property $plan is declared as type object<FS_Plugin_Plan>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
5424
5425
			$this->_store_site( true );
5426
		}
5427
5428
		/**
5429
		 * Track install's custom event.
5430
		 *
5431
		 * IMPORTANT:
5432
		 *      Custom event tracking is currently only supported for specific clients.
5433
		 *      If you are not one of them, please don't use this method. If you will,
5434
		 *      the API will simply ignore your request based on the plugin ID.
5435
		 *
5436
		 * Need custom tracking for your plugin or theme?
5437
		 *      If you are interested in custom event tracking please contact [email protected]
5438
		 *      for further details.
5439
		 *
5440
		 * @author Vova Feldman (@svovaf)
5441
		 * @since  1.2.1
5442
		 *
5443
		 * @param string $name       Event name.
5444
		 * @param array  $properties Associative key/value array with primitive values only
5445
		 * @param bool   $process_at A valid future date-time in the following format Y-m-d H:i:s.
5446
		 * @param bool   $once       If true, event will be tracked only once. IMPORTANT: Still trigger the API call.
5447
		 *
5448
		 * @return object|false Event data or FALSE on failure.
5449
		 *
5450
		 * @throws \Freemius_InvalidArgumentException
5451
		 */
5452
		public function track_event( $name, $properties = array(), $process_at = false, $once = false ) {
5453
			$this->_logger->entrance( http_build_query( array( 'name' => $name, 'once' => $once ) ) );
5454
5455
			if ( ! $this->is_registered() ) {
5456
				return false;
5457
			}
5458
5459
			$event = array( 'type' => $name );
5460
5461
			if ( is_numeric( $process_at ) && $process_at > time() ) {
5462
				$event['process_at'] = $process_at;
5463
			}
5464
5465
			if ( $once ) {
5466
				$event['once'] = true;
5467
			}
5468
5469
			if ( ! empty( $properties ) ) {
5470
				// Verify associative array values are primitive.
5471
				foreach ( $properties as $k => $v ) {
5472
					if ( ! is_scalar( $v ) ) {
5473
						throw new Freemius_InvalidArgumentException( 'The $properties argument must be an associative key/value array with primitive values only.' );
0 ignored issues
show
Documentation introduced by
'The $properties argumen...primitive values only.' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
5474
					}
5475
				}
5476
5477
				$event['properties'] = $properties;
5478
			}
5479
5480
			$result = $this->get_api_site_scope()->call( 'events.json', 'post', $event );
5481
5482
			return $this->is_api_error( $result ) ?
5483
				false :
5484
				$result;
5485
		}
5486
5487
		/**
5488
		 * Track install's custom event only once, but it still triggers the API call.
5489
		 *
5490
		 * IMPORTANT:
5491
		 *      Custom event tracking is currently only supported for specific clients.
5492
		 *      If you are not one of them, please don't use this method. If you will,
5493
		 *      the API will simply ignore your request based on the plugin ID.
5494
		 *
5495
		 * Need custom tracking for your plugin or theme?
5496
		 *      If you are interested in custom event tracking please contact [email protected]
5497
		 *      for further details.
5498
		 *
5499
		 * @author Vova Feldman (@svovaf)
5500
		 * @since  1.2.1
5501
		 *
5502
		 * @param string $name       Event name.
5503
		 * @param array  $properties Associative key/value array with primitive values only
5504
		 * @param bool   $process_at A valid future date-time in the following format Y-m-d H:i:s.
5505
		 *
5506
		 * @return object|false Event data or FALSE on failure.
5507
		 *
5508
		 * @throws \Freemius_InvalidArgumentException
5509
		 *
5510
		 * @user   Freemius::track_event()
5511
		 */
5512
		public function track_event_once( $name, $properties = array(), $process_at = false ) {
5513
			return $this->track_event( $name, $properties, $process_at, true );
5514
		}
5515
5516
		/**
5517
		 * Plugin uninstall hook.
5518
		 *
5519
		 * @author Vova Feldman (@svovaf)
5520
		 * @since  1.0.1
5521
		 *
5522
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
5523
		 */
5524
		function _uninstall_plugin_event( $check_user = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5525
			$this->_logger->entrance( 'slug = ' . $this->_slug );
5526
5527
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
5528
				return;
5529
			}
5530
5531
			$params           = array();
5532
			$uninstall_reason = null;
5533
			if ( isset( $this->_storage->uninstall_reason ) ) {
5534
				$uninstall_reason      = $this->_storage->uninstall_reason;
5535
				$params['reason_id']   = $uninstall_reason->id;
5536
				$params['reason_info'] = $uninstall_reason->info;
5537
			}
5538
5539
			if ( ! $this->is_registered() ) {
5540
				// Send anonymous uninstall event only if user submitted a feedback.
5541
				if ( isset( $uninstall_reason ) ) {
5542
					if ( isset( $uninstall_reason->is_anonymous ) && ! $uninstall_reason->is_anonymous ) {
5543
						$this->opt_in( false, false, false, false, true );
5544
					} else {
5545
						$params['uid'] = $this->get_anonymous_id();
5546
						$this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params );
5547
					}
5548
				}
5549
			} else {
5550
				// Send uninstall event.
5551
				$this->send_install_update( array_merge( $params, array(
5552
					'is_active'      => false,
5553
					'is_uninstalled' => true,
5554
				) ) );
5555
			}
5556
5557
			// @todo Decide if we want to delete plugin information from db.
5558
		}
5559
5560
		/**
5561
		 * @author Vova Feldman (@svovaf)
5562
		 * @since  1.1.1
5563
		 *
5564
		 * @return string
5565
		 */
5566
		function premium_plugin_basename() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5567
			return "{$this->_slug}-premium/" . basename( $this->_free_plugin_basename );
5568
		}
5569
5570
		/**
5571
		 * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking.
5572
		 *
5573
		 * @author Vova Feldman (@svovaf)
5574
		 * @since  1.0.2
5575
		 */
5576
		public static function _uninstall_plugin_hook() {
5577
			self::_load_required_static();
5578
5579
			self::$_static_logger->entrance();
5580
5581
			if ( ! current_user_can( 'activate_plugins' ) ) {
5582
				return;
5583
			}
5584
5585
			$plugin_file = substr( current_filter(), strlen( 'uninstall_' ) );
5586
5587
			self::$_static_logger->info( 'plugin = ' . $plugin_file );
5588
5589
			define( 'WP_FS__UNINSTALL_MODE', true );
5590
5591
			$fs = self::get_instance_by_file( $plugin_file );
5592
5593
			if ( is_object( $fs ) ) {
5594
				self::require_plugin_essentials();
5595
5596
				if ( is_plugin_active( $fs->_free_plugin_basename ) ||
5597
				     is_plugin_active( $fs->premium_plugin_basename() )
5598
				) {
5599
					// Deleting Free or Premium plugin version while the other version still installed.
5600
					return;
5601
				}
5602
5603
				$fs->_uninstall_plugin_event();
5604
5605
				$fs->do_action( 'after_uninstall' );
5606
			}
5607
		}
5608
5609
		#----------------------------------------------------------------------------------
5610
		#region Plugin Information
5611
		#----------------------------------------------------------------------------------
5612
5613
		/**
5614
		 * Load WordPress core plugin.php essential module.
5615
		 *
5616
		 * @author Vova Feldman (@svovaf)
5617
		 * @since  1.1.1
5618
		 */
5619
		private static function require_plugin_essentials() {
5620
			if ( ! function_exists( 'get_plugins' ) ) {
5621
				self::$_static_logger->log( 'Including wp-admin/includes/plugin.php...' );
5622
5623
				require_once ABSPATH . 'wp-admin/includes/plugin.php';
5624
			}
5625
		}
5626
5627
		/**
5628
		 * Load WordPress core pluggable.php module.
5629
		 *
5630
		 * @author Vova Feldman (@svovaf)
5631
		 * @since  1.1.2
5632
		 */
5633
		private static function require_pluggable_essentials() {
5634
			if ( ! function_exists( 'wp_get_current_user' ) ) {
5635
				require_once ABSPATH . 'wp-includes/pluggable.php';
5636
			}
5637
		}
5638
5639
		/**
5640
		 * Return plugin data.
5641
		 *
5642
		 * @author Vova Feldman (@svovaf)
5643
		 * @since  1.0.1
5644
		 *
5645
		 * @return array
5646
		 */
5647
		function get_plugin_data() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5648
			if ( ! isset( $this->_plugin_data ) ) {
5649
				self::require_plugin_essentials();
5650
5651
				if ( $this->is_plugin() ) {
5652
					/**
5653
					 * @author Vova Feldman (@svovaf)
5654
					 * @since  1.2.0 When using get_plugin_data() do NOT translate plugin data.
5655
					 *
5656
					 * @link   https://github.com/Freemius/wordpress-sdk/issues/77
5657
					 */
5658
					$plugin_data = get_plugin_data(
5659
						$this->_plugin_main_file_path,
5660
						false,
5661
						false
5662
					);
5663
				} else {
5664
					$theme_data = wp_get_theme();
5665
5666
					$plugin_data = array(
5667
						'Name'        => $theme_data->get( 'Name' ),
5668
						'Version'     => $theme_data->get( 'Version' ),
5669
						'Author'      => $theme_data->get( 'Author' ),
5670
						'Description' => $theme_data->get( 'Description' ),
5671
						'PluginURI'   => $theme_data->get( 'ThemeURI' ),
5672
					);
5673
				}
5674
5675
				$this->_plugin_data = $plugin_data;
5676
			}
5677
5678
			return $this->_plugin_data;
5679
		}
5680
5681
		/**
5682
		 * @author Vova Feldman (@svovaf)
5683
		 * @since  1.0.1
5684
		 * @since  1.2.2.5 If slug not set load slug by module ID.
5685
		 *
5686
		 * @return string Plugin slug.
5687
		 */
5688
		function get_slug() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5689
			if ( ! isset( $this->_slug ) ) {
5690
				$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
5691
				$this->_slug           = $id_slug_type_path_map[ $this->_module_id ]['slug'];
5692
			}
5693
5694
			return $this->_slug;
5695
		}
5696
5697
		/**
5698
		 * @author Vova Feldman (@svovaf)
5699
		 * @since  1.2.1.7
5700
		 *
5701
		 * @return string Plugin slug.
5702
		 */
5703
		function get_target_folder_name() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5704
			return $this->_slug . ( $this->can_use_premium_code() ? '-premium' : '' );
5705
		}
5706
5707
		/**
5708
		 * @author Vova Feldman (@svovaf)
5709
		 * @since  1.0.1
5710
		 *
5711
		 * @return number Plugin ID.
5712
		 */
5713
		function get_id() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5714
			return $this->_plugin->id;
5715
		}
5716
5717
		/**
5718
		 * @author Vova Feldman (@svovaf)
5719
		 * @since  1.2.1.5
5720
		 *
5721
		 * @return string Freemius SDK version
5722
		 */
5723
		function get_sdk_version() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5724
			return $this->version;
5725
		}
5726
5727
		/**
5728
		 * @author Vova Feldman (@svovaf)
5729
		 * @since  1.2.1.5
5730
		 *
5731
		 * @return number Parent plugin ID (if parent exist).
5732
		 */
5733
		function get_parent_id() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5734
			return $this->is_addon() ?
5735
				$this->get_parent_instance()->get_id() :
5736
				$this->_plugin->id;
5737
		}
5738
5739
		/**
5740
		 * @author Vova Feldman (@svovaf)
5741
		 * @since  1.0.1
5742
		 *
5743
		 * @return string Plugin public key.
5744
		 */
5745
		function get_public_key() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5746
			return $this->_plugin->public_key;
5747
		}
5748
5749
		/**
5750
		 * Will be available only on sandbox mode.
5751
		 *
5752
		 * @author Vova Feldman (@svovaf)
5753
		 * @since  1.0.4
5754
		 *
5755
		 * @return mixed Plugin secret key.
5756
		 */
5757
		function get_secret_key() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5758
			return $this->_plugin->secret_key;
5759
		}
5760
5761
		/**
5762
		 * @author Vova Feldman (@svovaf)
5763
		 * @since  1.1.1
5764
		 *
5765
		 * @return bool
5766
		 */
5767
		function has_secret_key() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5768
			return ! empty( $this->_plugin->secret_key );
5769
		}
5770
5771
		/**
5772
		 * @author Vova Feldman (@svovaf)
5773
		 * @since  1.0.9
5774
		 *
5775
		 * @return string
5776
		 */
5777
		function get_plugin_name() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5778
			$this->_logger->entrance();
5779
5780
			if ( ! isset( $this->_plugin_name ) ) {
5781
				$plugin_data = $this->get_plugin_data();
5782
5783
				// Get name.
5784
				$this->_plugin_name = $plugin_data['Name'];
5785
5786
				// Check if plugin name contains "(Premium)" suffix and remove it.
5787
				$suffix     = ' (premium)';
5788
				$suffix_len = strlen( $suffix );
5789
5790
				if ( strlen( $plugin_data['Name'] ) > $suffix_len &&
5791
				     $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len )
5792
				) {
5793
					$this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len );
5794
				}
5795
5796
				$this->_logger->departure( 'Name = ' . $this->_plugin_name );
5797
			}
5798
5799
			return $this->_plugin_name;
5800
		}
5801
5802
		/**
5803
		 * @author Vova Feldman (@svovaf)
5804
		 * @since  1.0.0
5805
		 *
5806
		 * @return string
5807
		 */
5808
		function get_plugin_version() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5809
			$this->_logger->entrance();
5810
5811
			$plugin_data = $this->get_plugin_data();
5812
5813
			$this->_logger->departure( 'Version = ' . $plugin_data['Version'] );
5814
5815
			return $this->apply_filters( 'plugin_version', $plugin_data['Version'] );
5816
		}
5817
5818
		/**
5819
		 * @author Vova Feldman (@svovaf)
5820
		 * @since  1.2.1.7
5821
		 *
5822
		 * @return string
5823
		 */
5824
		function get_plugin_title() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5825
			$this->_logger->entrance();
5826
5827
			$title = $this->_plugin->title;
5828
5829
			return $this->apply_filters( 'plugin_title', $title );
5830
		}
5831
5832
		/**
5833
		 * @author Vova Feldman (@svovaf)
5834
		 * @since 1.2.2.7
5835
		 *
5836
		 * @param bool $lowercase
5837
		 *
5838
		 * @return string
5839
		 */
5840
		function get_module_label( $lowercase = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5841
			$label = $this->is_addon() ?
5842
				$this->get_text( 'addon' ) :
5843
				( $this->is_plugin() ?
5844
					$this->get_text( 'plugin' ) :
5845
					$this->get_text( 'theme' ) );
5846
5847
			if ( $lowercase ) {
5848
				$label = strtolower( $label );
5849
			}
5850
5851
			return $label;
5852
		}
5853
5854
		/**
5855
		 * @author Vova Feldman (@svovaf)
5856
		 * @since  1.0.4
5857
		 *
5858
		 * @return string
5859
		 */
5860
		function get_plugin_basename() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5861
			if ( ! isset( $this->_plugin_basename ) ) {
5862
				if ( $this->is_plugin() ) {
5863
					$this->_plugin_basename = plugin_basename( $this->_plugin_main_file_path );
5864
				} else {
5865
					$this->_plugin_basename = basename( dirname( $this->_plugin_main_file_path ) );
5866
				}
5867
			}
5868
5869
			return $this->_plugin_basename;
5870
		}
5871
5872
		function get_plugin_folder_name() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5873
			$this->_logger->entrance();
5874
5875
			$plugin_folder = $this->_plugin_basename;
5876
5877
			while ( '.' !== dirname( $plugin_folder ) ) {
5878
				$plugin_folder = dirname( $plugin_folder );
5879
			}
5880
5881
			$this->_logger->departure( 'Folder Name = ' . $plugin_folder );
5882
5883
			return $plugin_folder;
5884
		}
5885
5886
		#endregion ------------------------------------------------------------------
5887
5888
		/* Account
5889
		------------------------------------------------------------------------------------------------------------------*/
5890
5891
		/**
5892
		 * Find plugin's slug by plugin's basename.
5893
		 *
5894
		 * @author Vova Feldman (@svovaf)
5895
		 * @since  1.0.9
5896
		 *
5897
		 * @param string $plugin_base_name
5898
		 *
5899
		 * @return false|string
5900
		 */
5901
		private static function find_slug_by_basename( $plugin_base_name ) {
5902
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
5903
5904
			if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) {
5905
				return false;
5906
			}
5907
5908
			return $file_slug_map[ $plugin_base_name ];
5909
		}
5910
5911
		/**
5912
		 * Store the map between the plugin's basename to the slug.
5913
		 *
5914
		 * @author Vova Feldman (@svovaf)
5915
		 * @since  1.0.9
5916
		 */
5917
		private function store_file_slug_map() {
5918
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
5919
5920
			if ( ! array( $file_slug_map ) ) {
5921
				$file_slug_map = array();
5922
			}
5923
5924
			if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) ||
5925
			     $file_slug_map[ $this->_plugin_basename ] !== $this->_slug
5926
			) {
5927
				$file_slug_map[ $this->_plugin_basename ] = $this->_slug;
5928
				self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true );
5929
			}
5930
		}
5931
5932
		/**
5933
		 * @return FS_User[]
5934
		 */
5935
		static function get_all_users() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5936
			$users = self::$_accounts->get_option( 'users', array() );
5937
5938
			if ( ! is_array( $users ) ) {
5939
				$users = array();
5940
			}
5941
5942
			return $users;
5943
		}
5944
5945
		/**
5946
		 * @param string $module_type
5947
		 *
5948
		 * @return FS_Site[]
5949
		 */
5950
		private static function get_all_sites( $module_type = WP_FS__MODULE_TYPE_PLUGIN ) {
5951
			$sites = self::get_account_option( 'sites', $module_type );
5952
5953
			if ( ! is_array( $sites ) ) {
5954
				$sites = array();
5955
			}
5956
5957
			return $sites;
5958
		}
5959
5960
		/**
5961
		 * @author Leo Fajardo (@leorw)
5962
		 *
5963
		 * @since  1.2.2
5964
		 *
5965
		 * @param string $option_name
5966
		 * @param string $module_type
5967
		 *
5968
		 * @return mixed
5969
		 */
5970
		private static function get_account_option( $option_name, $module_type ) {
5971
			if ( WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) {
5972
				$option_name = $module_type . '_' . $option_name;
5973
			}
5974
5975
			return self::$_accounts->get_option( $option_name, array() );
5976
		}
5977
5978
		/**
5979
		 * @author Leo Fajardo (@leorw)
5980
		 *
5981
		 * @since  1.2.2
5982
		 *
5983
		 * @param string $option_name
5984
		 * @param mixed  $option_value
5985
		 * @param bool   $store
5986
		 */
5987
		private function set_account_option( $option_name, $option_value, $store ) {
5988
			self::set_account_option_by_module(
5989
				$this->_module_type,
5990
				$option_name,
5991
				$option_value,
5992
				$store
5993
			);
5994
		}
5995
5996
		/**
5997
		 * @author Vova Feldman (@svovaf)
5998
		 *
5999
		 * @since  1.2.2.7
6000
		 *
6001
		 * @param string $module_type
6002
		 * @param string $option_name
6003
		 * @param mixed  $option_value
6004
		 * @param bool   $store
6005
		 */
6006
		private static function set_account_option_by_module( $module_type, $option_name, $option_value, $store ) {
6007
			if ( WP_FS__MODULE_TYPE_PLUGIN != $module_type ) {
6008
				$option_name = $module_type . '_' . $option_name;
6009
			}
6010
6011
			self::$_accounts->set_option( $option_name, $option_value, $store );
6012
		}
6013
6014
		/**
6015
		 * @author Vova Feldman (@svovaf)
6016
		 * @since  1.0.6
6017
		 *
6018
		 * @param string $module_type
6019
		 *
6020
		 * @return FS_Plugin_License[]
6021
		 */
6022
		private static function get_all_licenses( $module_type = WP_FS__MODULE_TYPE_PLUGIN ) {
6023
			$licenses = self::get_account_option( 'licenses', $module_type );
6024
6025
			if ( ! is_array( $licenses ) ) {
6026
				$licenses = array();
6027
			}
6028
6029
			return $licenses;
6030
		}
6031
6032
		/**
6033
		 * @param string|bool $module_type
6034
		 *
6035
		 * @return FS_Plugin_Plan[]
6036
		 */
6037
		private static function get_all_plans( $module_type = false ) {
6038
			$plans = self::get_account_option( 'plans', $module_type );
0 ignored issues
show
Bug introduced by
It seems like $module_type defined by parameter $module_type on line 6037 can also be of type boolean; however, Freemius::get_account_option() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
6039
6040
			if ( ! is_array( $plans ) ) {
6041
				$plans = array();
6042
			}
6043
6044
			return $plans;
6045
		}
6046
6047
		/**
6048
		 * @author Vova Feldman (@svovaf)
6049
		 * @since  1.0.4
6050
		 *
6051
		 * @return FS_Plugin_Tag[]
6052
		 */
6053
		private static function get_all_updates() {
6054
			$updates = self::$_accounts->get_option( 'updates', array() );
6055
6056
			if ( ! is_array( $updates ) ) {
6057
				$updates = array();
6058
			}
6059
6060
			return $updates;
6061
		}
6062
6063
		/**
6064
		 * @author Vova Feldman (@svovaf)
6065
		 * @since  1.0.6
6066
		 *
6067
		 * @return array<number,FS_Plugin[]>|false
6068
		 */
6069
		private static function get_all_addons() {
6070
			$addons = self::$_accounts->get_option( 'addons', array() );
6071
6072
			if ( ! is_array( $addons ) ) {
6073
				$addons = array();
6074
			}
6075
6076
			return $addons;
6077
		}
6078
6079
		/**
6080
		 * @author Vova Feldman (@svovaf)
6081
		 * @since  1.0.6
6082
		 *
6083
		 * @return FS_Plugin[]|false
6084
		 */
6085
		private static function get_all_account_addons() {
6086
			$addons = self::$_accounts->get_option( 'account_addons', array() );
6087
6088
			if ( ! is_array( $addons ) ) {
6089
				$addons = array();
6090
			}
6091
6092
			return $addons;
6093
		}
6094
6095
		/**
6096
		 * Check if user has connected his account (opted-in).
6097
		 *
6098
		 * Note:
6099
		 *      If the user opted-in and opted-out on a later stage,
6100
		 *      this will still return true. If you want to check if the
6101
		 *      user is currently opted-in, use:
6102
		 *          `$fs->is_registered() && $fs->is_tracking_allowed()`
6103
		 *
6104
		 * @author Vova Feldman (@svovaf)
6105
		 * @since  1.0.1
6106
		 * @return bool
6107
		 */
6108
		function is_registered() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6109
			return is_object( $this->_user );
6110
		}
6111
6112
		/**
6113
		 * Returns TRUE if the user opted-in and didn't disconnect (opt-out).
6114
		 *
6115
		 * @author Leo Fajardo (@leorw)
6116
		 * @since  1.2.1.5
6117
		 *
6118
		 * @return bool
6119
		 */
6120
		function is_tracking_allowed() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6121
			return ( is_object( $this->_site ) && true !== $this->_site->is_disconnected );
6122
		}
6123
6124
		/**
6125
		 * @author Vova Feldman (@svovaf)
6126
		 * @since  1.0.4
6127
		 *
6128
		 * @return FS_Plugin
6129
		 */
6130
		function get_plugin() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6131
			return $this->_plugin;
6132
		}
6133
6134
		/**
6135
		 * @author Vova Feldman (@svovaf)
6136
		 * @since  1.0.3
6137
		 *
6138
		 * @return FS_User
6139
		 */
6140
		function get_user() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6141
			return $this->_user;
6142
		}
6143
6144
		/**
6145
		 * @author Vova Feldman (@svovaf)
6146
		 * @since  1.0.3
6147
		 *
6148
		 * @return FS_Site
6149
		 */
6150
		function get_site() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6151
			return $this->_site;
6152
		}
6153
6154
		/**
6155
		 * Get plugin add-ons.
6156
		 *
6157
		 * @author Vova Feldman (@svovaf)
6158
		 * @since  1.0.6
6159
		 *
6160
		 * @since  1.1.7.3 If not yet loaded, fetch data from the API.
6161
		 *
6162
		 * @param bool $flush
6163
		 *
6164
		 * @return FS_Plugin[]|false
6165
		 */
6166
		function get_addons( $flush = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6167
			$this->_logger->entrance();
6168
6169
			if ( ! $this->_has_addons ) {
6170
				return false;
6171
			}
6172
6173
			$addons = $this->sync_addons( $flush );
6174
6175
			return ( ! is_array( $addons ) || empty( $addons ) ) ?
6176
				false :
6177
				$addons;
6178
		}
6179
6180
		/**
6181
		 * @author Vova Feldman (@svovaf)
6182
		 * @since  1.0.6
6183
		 *
6184
		 * @return FS_Plugin[]|false
6185
		 */
6186
		function get_account_addons() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6187
			$this->_logger->entrance();
6188
6189
			$addons = self::get_all_account_addons();
6190
6191
			if ( ! is_array( $addons ) ||
6192
			     ! isset( $addons[ $this->_plugin->id ] ) ||
6193
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
6194
			     0 === count( $addons[ $this->_plugin->id ] )
6195
			) {
6196
				return false;
6197
			}
6198
6199
			return $addons[ $this->_plugin->id ];
6200
		}
6201
6202
		/**
6203
		 * Check if user has any
6204
		 *
6205
		 * @author Vova Feldman (@svovaf)
6206
		 * @since  1.1.6
6207
		 *
6208
		 * @return bool
6209
		 */
6210
		function has_account_addons() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6211
			$addons = $this->get_account_addons();
6212
6213
			return is_array( $addons ) && ( 0 < count( $addons ) );
6214
		}
6215
6216
6217
		/**
6218
		 * Get add-on by ID (from local data).
6219
		 *
6220
		 * @author Vova Feldman (@svovaf)
6221
		 * @since  1.0.6
6222
		 *
6223
		 * @param number $id
6224
		 *
6225
		 * @return FS_Plugin|false
6226
		 */
6227
		function get_addon( $id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6228
			$this->_logger->entrance();
6229
6230
			$addons = $this->get_addons();
6231
6232
			if ( is_array( $addons ) ) {
6233
				foreach ( $addons as $addon ) {
6234
					if ( $id == $addon->id ) {
6235
						return $addon;
6236
					}
6237
				}
6238
			}
6239
6240
			return false;
6241
		}
6242
6243
		/**
6244
		 * Get add-on by slug (from local data).
6245
		 *
6246
		 * @author Vova Feldman (@svovaf)
6247
		 * @since  1.0.6
6248
		 *
6249
		 * @param string $slug
6250
		 *
6251
		 * @param bool   $flush
6252
		 *
6253
		 * @return FS_Plugin|false
6254
		 */
6255
		function get_addon_by_slug( $slug, $flush = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6256
			$this->_logger->entrance();
6257
6258
			$addons = $this->get_addons( $flush );
6259
6260
			if ( is_array( $addons ) ) {
6261
				foreach ( $addons as $addon ) {
6262
					if ( $slug === $addon->slug ) {
6263
						return $addon;
6264
					}
6265
				}
6266
			}
6267
6268
			return false;
6269
		}
6270
6271
		#----------------------------------------------------------------------------------
6272
		#region Plans & Licensing
6273
		#----------------------------------------------------------------------------------
6274
6275
		/**
6276
		 * Check if running premium plugin code.
6277
		 *
6278
		 * @author Vova Feldman (@svovaf)
6279
		 * @since  1.0.5
6280
		 *
6281
		 * @return bool
6282
		 */
6283
		function is_premium() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6284
			return $this->_plugin->is_premium;
6285
		}
6286
6287
		/**
6288
		 * Get site's plan ID.
6289
		 *
6290
		 * @author Vova Feldman (@svovaf)
6291
		 * @since  1.0.2
6292
		 *
6293
		 * @return number
6294
		 */
6295
		function get_plan_id() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6296
			return $this->_site->plan->id;
6297
		}
6298
6299
		/**
6300
		 * Get site's plan title.
6301
		 *
6302
		 * @author Vova Feldman (@svovaf)
6303
		 * @since  1.0.2
6304
		 *
6305
		 * @return string
6306
		 */
6307
		function get_plan_title() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6308
			return $this->_site->plan->title;
6309
		}
6310
6311
		/**
6312
		 * @author Vova Feldman (@svovaf)
6313
		 * @since  1.0.9
6314
		 *
6315
		 * @return FS_Plugin_Plan|false
6316
		 */
6317
		function get_plan() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6318
			return is_object( $this->_site->plan ) ?
6319
				$this->_site->plan :
6320
				false;
6321
		}
6322
6323
		/**
6324
		 * @author Vova Feldman (@svovaf)
6325
		 * @since  1.0.3
6326
		 *
6327
		 * @return bool
6328
		 */
6329
		function is_trial() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6330
			$this->_logger->entrance();
6331
6332
			if ( ! $this->is_registered() ) {
6333
				return false;
6334
			}
6335
6336
			return $this->_site->is_trial();
6337
		}
6338
6339
		/**
6340
		 * Check if currently in a trial with payment method (credit card or paypal).
6341
		 *
6342
		 * @author Vova Feldman (@svovaf)
6343
		 * @since  1.1.7
6344
		 *
6345
		 * @return bool
6346
		 */
6347
		function is_paid_trial() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6348
			$this->_logger->entrance();
6349
6350
			if ( ! $this->is_trial() ) {
6351
				return false;
6352
			}
6353
6354
			return $this->has_active_valid_license() && ( $this->_site->trial_plan_id == $this->_license->plan_id );
6355
		}
6356
6357
		/**
6358
		 * Check if trial already utilized.
6359
		 *
6360
		 * @since 1.0.9
6361
		 *
6362
		 * @return bool
6363
		 */
6364
		function is_trial_utilized() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6365
			$this->_logger->entrance();
6366
6367
			if ( ! $this->is_registered() ) {
6368
				return false;
6369
			}
6370
6371
			return $this->_site->is_trial_utilized();
6372
		}
6373
6374
		/**
6375
		 * Get trial plan information (if in trial).
6376
		 *
6377
		 * @author Vova Feldman (@svovaf)
6378
		 * @since  1.0.9
6379
		 *
6380
		 * @return bool|FS_Plugin_Plan
6381
		 */
6382
		function get_trial_plan() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6383
			$this->_logger->entrance();
6384
6385
			if ( ! $this->is_trial() ) {
6386
				return false;
6387
			}
6388
6389
			return $this->_storage->trial_plan;
6390
		}
6391
6392
		/**
6393
		 * Check if the user has an activate, non-expired license on current plugin's install.
6394
		 *
6395
		 * @since 1.0.9
6396
		 *
6397
		 * @return bool
6398
		 */
6399
		function is_paying() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6400
			$this->_logger->entrance();
6401
6402
			if ( ! $this->is_registered() ) {
6403
				return false;
6404
			}
6405
6406
			if ( ! $this->has_paid_plan() ) {
6407
				return false;
6408
			}
6409
6410
			return (
6411
				! $this->is_trial() &&
6412
				'free' !== $this->_site->plan->name &&
6413
				$this->has_active_valid_license()
6414
			);
6415
		}
6416
6417
		/**
6418
		 * @author Vova Feldman (@svovaf)
6419
		 * @since  1.0.4
6420
		 *
6421
		 * @return bool
6422
		 */
6423
		function is_free_plan() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6424
			if ( ! $this->is_registered() ) {
6425
				return true;
6426
			}
6427
6428
			if ( ! $this->has_paid_plan() ) {
6429
				return true;
6430
			}
6431
6432
			return (
6433
				'free' === $this->_site->plan->name ||
6434
				! $this->has_features_enabled_license()
6435
			);
6436
		}
6437
6438
		/**
6439
		 * @author Vova Feldman (@svovaf)
6440
		 * @since  1.0.5
6441
		 *
6442
		 * @return bool
6443
		 */
6444
		function _has_premium_license() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6445
			$this->_logger->entrance();
6446
6447
			$premium_license = $this->_get_available_premium_license();
6448
6449
			return ( false !== $premium_license );
6450
		}
6451
6452
		/**
6453
		 * Check if user has any licenses associated with the plugin (including expired or blocking).
6454
		 *
6455
		 * @author Vova Feldman (@svovaf)
6456
		 * @since  1.1.7.3
6457
		 *
6458
		 * @return bool
6459
		 */
6460
		private function has_any_license() {
6461
			return is_array( $this->_licenses ) && ( 0 < count( $this->_licenses ) );
6462
		}
6463
6464
		/**
6465
		 * @author Vova Feldman (@svovaf)
6466
		 * @since  1.0.5
6467
		 *
6468
		 * @return FS_Plugin_License|false
6469
		 */
6470
		function _get_available_premium_license() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6471
			$this->_logger->entrance();
6472
6473
			if ( ! $this->has_paid_plan() ) {
6474
				return false;
6475
			}
6476
6477
			if ( is_array( $this->_licenses ) ) {
6478
				foreach ( $this->_licenses as $license ) {
6479
					if ( ! $license->is_utilized() && $license->is_features_enabled() ) {
6480
						return $license;
6481
					}
6482
				}
6483
			}
6484
6485
			return false;
6486
		}
6487
6488
		/**
6489
		 * Sync local plugin plans with remote server.
6490
		 *
6491
		 * @author Vova Feldman (@svovaf)
6492
		 * @since  1.0.5
6493
		 *
6494
		 * @return FS_Plugin_Plan[]|object
6495
		 */
6496
		function _sync_plans() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6497
			$plans = $this->_fetch_plugin_plans();
6498
6499
			if ( $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) {
6500
				$this->_plans = $plans;
0 ignored issues
show
Documentation Bug introduced by
It seems like $plans can also be of type object. However, the property $_plans is declared as type array<integer,object<FS_Plugin_Plan>>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
6501
				$this->_store_plans();
6502
			}
6503
6504
			$this->do_action( 'after_plans_sync', $plans );
6505
6506
			return $this->_plans;
6507
		}
6508
6509
		/**
6510
		 * @author Vova Feldman (@svovaf)
6511
		 * @since  1.0.5
6512
		 *
6513
		 * @param number $id
6514
		 *
6515
		 * @return FS_Plugin_Plan|false
6516
		 */
6517
		function _get_plan_by_id( $id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6518
			$this->_logger->entrance();
6519
6520
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
6521
				$this->_sync_plans();
6522
			}
6523
6524
			foreach ( $this->_plans as $plan ) {
6525
				if ( $id == $plan->id ) {
6526
					return $plan;
6527
				}
6528
			}
6529
6530
			return false;
6531
		}
6532
6533
		/**
6534
		 * @author Vova Feldman (@svovaf)
6535
		 * @since  1.1.8.1
6536
		 *
6537
		 * @param string $name
6538
		 *
6539
		 * @return FS_Plugin_Plan|false
6540
		 */
6541
		private function get_plan_by_name( $name ) {
6542
			$this->_logger->entrance();
6543
6544
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
6545
				$this->_sync_plans();
6546
			}
6547
6548
			foreach ( $this->_plans as $plan ) {
6549
				if ( $name == $plan->name ) {
6550
					return $plan;
6551
				}
6552
			}
6553
6554
			return false;
6555
		}
6556
6557
		/**
6558
		 * Sync local plugin plans with remote server.
6559
		 *
6560
		 * @author Vova Feldman (@svovaf)
6561
		 * @since  1.0.6
6562
		 *
6563
		 * @param number|bool $site_license_id
6564
		 *
6565
		 * @return FS_Plugin_License[]|object
6566
		 */
6567
		function _sync_licenses( $site_license_id = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6568
			$licenses = $this->_fetch_licenses( false, $site_license_id );
6569
6570
			if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) {
6571
				$this->_licenses = $licenses;
0 ignored issues
show
Documentation Bug introduced by
It seems like $licenses can also be of type object. However, the property $_licenses is declared as type array<integer,object<FS_Plugin_License>>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
6572
				$this->_store_licenses();
6573
			}
6574
6575
			// Update current license.
6576
			if ( is_object( $this->_license ) ) {
6577
				$this->_license = $this->_get_license_by_id( $this->_license->id );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->_get_license_by_id($this->_license->id) can also be of type false. However, the property $_license is declared as type object<FS_Plugin_License>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
6578
			}
6579
6580
			return $this->_licenses;
6581
		}
6582
6583
		/**
6584
		 * @author Vova Feldman (@svovaf)
6585
		 * @since  1.0.5
6586
		 *
6587
		 * @param number $id
6588
		 *
6589
		 * @return FS_Plugin_License|false
6590
		 */
6591
		function _get_license_by_id( $id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6592
			$this->_logger->entrance();
6593
6594
			if ( ! is_numeric( $id ) ) {
6595
				return false;
6596
			}
6597
6598
			if ( ! $this->has_any_license() ) {
6599
				$this->_sync_licenses();
6600
			}
6601
6602
			foreach ( $this->_licenses as $license ) {
6603
				if ( $id == $license->id ) {
6604
					return $license;
6605
				}
6606
			}
6607
6608
			return false;
6609
		}
6610
6611
		/**
6612
		 * Sync site's license with user licenses.
6613
		 *
6614
		 * @author Vova Feldman (@svovaf)
6615
		 * @since  1.0.6
6616
		 *
6617
		 * @param FS_Plugin_License|null $new_license
6618
		 */
6619
		function _update_site_license( $new_license ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6620
			$this->_logger->entrance();
6621
6622
			$this->_license = $new_license;
6623
6624
			if ( ! is_object( $new_license ) ) {
6625
				$this->_site->license_id = null;
6626
				$this->_sync_site_subscription( null );
6627
6628
				return;
6629
			}
6630
6631
			$this->_site->license_id = $this->_license->id;
6632
6633
			if ( ! is_array( $this->_licenses ) ) {
6634
				$this->_licenses = array();
6635
			}
6636
6637
			$is_license_found = false;
6638
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
6639
				if ( $new_license->id == $this->_licenses[ $i ]->id ) {
6640
					$this->_licenses[ $i ] = $new_license;
6641
6642
					$is_license_found = true;
6643
					break;
6644
				}
6645
			}
6646
6647
			// If new license just append.
6648
			if ( ! $is_license_found ) {
6649
				$this->_licenses[] = $new_license;
6650
			}
6651
6652
			$this->_sync_site_subscription( $new_license );
6653
		}
6654
6655
		/**
6656
		 * Sync site's subscription.
6657
		 *
6658
		 * @author Vova Feldman (@svovaf)
6659
		 * @since  1.0.9
6660
		 *
6661
		 * @param FS_Plugin_License|null $license
6662
		 *
6663
		 * @return bool|\FS_Subscription
6664
		 */
6665
		private function _sync_site_subscription( $license ) {
6666
			if ( ! is_object( $license ) ) {
6667
				unset( $this->_storage->subscription );
6668
6669
				return false;
6670
			}
6671
6672
			// Load subscription details if not lifetime.
6673
			$subscription = $license->is_lifetime() ?
6674
				false :
6675
				$this->_fetch_site_license_subscription();
6676
6677
			if ( is_object( $subscription ) && ! isset( $subscription->error ) ) {
6678
				$this->_storage->subscription = $subscription;
6679
			} else {
6680
				unset( $this->_storage->subscription );
6681
			}
6682
6683
			return $subscription;
6684
		}
6685
6686
		/**
6687
		 * @author Vova Feldman (@svovaf)
6688
		 * @since  1.0.6
6689
		 *
6690
		 * @return bool|\FS_Plugin_License
6691
		 */
6692
		function _get_license() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6693
			return $this->_license;
6694
		}
6695
6696
		/**
6697
		 * @return bool|\FS_Subscription
6698
		 */
6699
		function _get_subscription() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6700
			return isset( $this->_storage->subscription ) ?
6701
				$this->_storage->subscription :
6702
				false;
6703
		}
6704
6705
		/**
6706
		 * @author Vova Feldman (@svovaf)
6707
		 * @since  1.0.2
6708
		 *
6709
		 * @param string $plan  Plan name
6710
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
6711
		 *
6712
		 * @return bool
6713
		 */
6714
		function is_plan( $plan, $exact = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6715
			$this->_logger->entrance();
6716
6717
			if ( ! $this->is_registered() ) {
6718
				return false;
6719
			}
6720
6721
			$plan = strtolower( $plan );
6722
6723
			if ( $this->_site->plan->name === $plan ) // Exact plan.
6724
			{
6725
				return true;
6726
			} else if ( $exact ) // Required exact, but plans are different.
6727
			{
6728
				return false;
6729
			}
6730
6731
			$current_plan_order  = - 1;
6732
			$required_plan_order = - 1;
6733
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
6734
				if ( $plan === $this->_plans[ $i ]->name ) {
6735
					$required_plan_order = $i;
6736
				} else if ( $this->_site->plan->name === $this->_plans[ $i ]->name ) {
6737
					$current_plan_order = $i;
6738
				}
6739
			}
6740
6741
			return ( $current_plan_order > $required_plan_order );
6742
		}
6743
6744
		/**
6745
		 * Check if module has only one plan.
6746
		 *
6747
		 * @author Vova Feldman (@svovaf)
6748
		 * @since  1.2.1.7
6749
		 *
6750
		 * @return bool
6751
		 */
6752
		function is_single_plan() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6753
			$this->_logger->entrance();
6754
6755
			if ( ! $this->is_registered() ||
6756
			     ! is_array( $this->_plans ) ||
6757
			     0 === count( $this->_plans )
6758
			) {
6759
				return true;
6760
			}
6761
6762
			return ( 1 === count( $this->_plans ) );
6763
		}
6764
6765
		/**
6766
		 * Check if plan based on trial. If not in trial mode, should return false.
6767
		 *
6768
		 * @since  1.0.9
6769
		 *
6770
		 * @param string $plan  Plan name
6771
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
6772
		 *
6773
		 * @return bool
6774
		 */
6775
		function is_trial_plan( $plan, $exact = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6776
			$this->_logger->entrance();
6777
6778
			if ( ! $this->is_registered() ) {
6779
				return false;
6780
			}
6781
6782
			if ( ! $this->is_trial() ) {
6783
				return false;
6784
			}
6785
6786
			if ( ! isset( $this->_storage->trial_plan ) ) {
6787
				// Store trial plan information.
6788
				$this->_enrich_site_trial_plan( true );
6789
			}
6790
6791
			if ( $this->_storage->trial_plan->name === $plan ) // Exact plan.
6792
			{
6793
				return true;
6794
			} else if ( $exact ) // Required exact, but plans are different.
6795
			{
6796
				return false;
6797
			}
6798
6799
			$current_plan_order  = - 1;
6800
			$required_plan_order = - 1;
6801
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
6802
				if ( $plan === $this->_plans[ $i ]->name ) {
6803
					$required_plan_order = $i;
6804
				} else if ( $this->_storage->trial_plan->name === $this->_plans[ $i ]->name ) {
6805
					$current_plan_order = $i;
6806
				}
6807
			}
6808
6809
			return ( $current_plan_order > $required_plan_order );
6810
		}
6811
6812
		/**
6813
		 * Check if plugin has any paid plans.
6814
		 *
6815
		 * @author Vova Feldman (@svovaf)
6816
		 * @since  1.0.7
6817
		 *
6818
		 * @return bool
6819
		 */
6820
		function has_paid_plan() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6821
			return $this->_has_paid_plans ||
6822
			       FS_Plan_Manager::instance()->has_paid_plan( $this->_plans );
6823
		}
6824
6825
		/**
6826
		 * Check if plugin has any plan with a trail.
6827
		 *
6828
		 * @author Vova Feldman (@svovaf)
6829
		 * @since  1.0.9
6830
		 *
6831
		 * @return bool
6832
		 */
6833
		function has_trial_plan() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6834
			if ( ! $this->is_registered() ) {
6835
				/**
6836
				 * @author Vova Feldman(@svovaf)
6837
				 * @since  1.2.1.5
6838
				 *
6839
				 * Allow setting a trial from the SDK without calling the API.
6840
				 * But, if the user did opt-in, continue using the real data from the API.
6841
				 */
6842
				if ( $this->_trial_days >= 0 ) {
6843
					return true;
6844
				}
6845
6846
				return false;
6847
			}
6848
6849
			return $this->_storage->get( 'has_trial_plan', false );
6850
		}
6851
6852
		/**
6853
		 * Check if plugin has any free plan, or is it premium only.
6854
		 *
6855
		 * Note: If no plans configured, assume plugin is free.
6856
		 *
6857
		 * @author Vova Feldman (@svovaf)
6858
		 * @since  1.0.7
6859
		 *
6860
		 * @return bool
6861
		 */
6862
		function has_free_plan() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6863
			return ! $this->is_only_premium();
6864
		}
6865
6866
		/**
6867
		 * Displays a license activation dialog box when the user clicks on the "Activate License"
6868
		 * or "Change License" link on the plugins
6869
		 * page.
6870
		 *
6871
		 * @author Leo Fajardo (@leorw)
6872
		 * @since  1.1.9
6873
		 */
6874
		function _add_license_activation_dialog_box() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6875
			$vars = array(
6876
				'id' => $this->_module_id,
6877
			);
6878
6879
			fs_require_template( 'forms/license-activation.php', $vars );
6880
			fs_require_template( 'forms/resend-key.php', $vars );
6881
		}
6882
6883
		/**
6884
		 * Displays the opt-out dialog box when the user clicks on the "Opt Out" link on the "Plugins"
6885
		 * page.
6886
		 *
6887
		 * @author Leo Fajardo (@leorw)
6888
		 * @since  1.2.1.5
6889
		 */
6890
		function _add_optout_dialog() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6891
			if ( $this->is_theme() ) {
6892
				$vars = null;
6893
				fs_require_once_template( '/js/jquery.content-change.php', $vars );
6894
			}
6895
6896
			$vars = array( 'id' => $this->_module_id );
6897
			fs_require_template( 'forms/optout.php', $vars );
6898
		}
6899
6900
		/**
6901
		 * Prepare page to include all required UI and logic for the license activation dialog.
6902
		 *
6903
		 * @author Vova Feldman (@svovaf)
6904
		 * @since  1.2.0
6905
		 */
6906
		function _add_license_activation() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6907
			if ( ! $this->is_user_admin() ) {
6908
				// Only admins can activate a license.
6909
				return;
6910
			}
6911
6912
			if ( ! $this->has_paid_plan() ) {
6913
				// Module doesn't have any paid plans.
6914
				return;
6915
			}
6916
6917
			if ( ! $this->is_premium() ) {
6918
				// Only add license activation logic to the premium version.
6919
				return;
6920
			}
6921
6922
			// Add license activation link and AJAX request handler.
6923
			if ( self::is_plugins_page() ) {
6924
				/**
6925
				 * @since 1.2.0 Add license action link only on plugins page.
6926
				 */
6927
				$this->_add_license_action_link();
6928
			}
6929
6930
			// Add license activation AJAX callback.
6931
			$this->add_ajax_action( 'activate_license', array( &$this, '_activate_license_ajax_action' ) );
6932
6933
			// Add resend license AJAX callback.
6934
			$this->add_ajax_action( 'resend_license_key', array( &$this, '_resend_license_key_ajax_action' ) );
6935
		}
6936
6937
		/**
6938
		 * @author Leo Fajardo (@leorw)
6939
		 * @since  1.1.9
6940
		 */
6941
		function _activate_license_ajax_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6942
			$this->_logger->entrance();
6943
6944
			$this->check_ajax_referer( 'activate_license' );
6945
6946
			$license_key = trim( fs_request_get( 'license_key' ) );
6947
6948
			if ( empty( $license_key ) ) {
6949
				exit;
6950
			}
6951
6952
			$plugin_id = fs_request_get( 'module_id', '', 'post' );
6953
			$fs        = ( $plugin_id == $this->_module_id ) ?
6954
				$this :
6955
				$this->get_addon_instance( $plugin_id );
6956
6957
			$error     = false;
6958
			$next_page = false;
6959
6960
			if ( $fs->is_registered() ) {
6961
				$api     = $fs->get_api_site_scope();
6962
				$install = $api->call( '/', 'put', array(
6963
					'license_key' => $fs->apply_filters( 'license_key', $license_key )
6964
				) );
6965
6966
				if ( isset( $install->error ) ) {
6967
					$error = $install->error->message;
6968
				} else {
6969
                    $fs->_sync_license( true );
6970
6971
                    $next_page = $fs->is_addon() ?
6972
                        $fs->get_parent_instance()->get_account_url() :
6973
                        $fs->get_account_url();
6974
6975
                    $fs->reconnect_locally();
6976
				}
6977
			} else {
6978
				$next_page = $fs->opt_in( false, false, false, $license_key );
6979
6980
				if ( isset( $next_page->error ) ) {
6981
					$error = $next_page->error;
6982
				}
6983
			}
6984
6985
			$result = array(
6986
				'success' => ( false === $error )
6987
			);
6988
6989
			if ( false !== $error ) {
6990
				$result['error'] = $error;
6991
			} else {
6992
				$result['next_page'] = $next_page;
6993
			}
6994
6995
			echo json_encode( $result );
6996
6997
			exit;
6998
		}
6999
7000
		/**
7001
		 * Billing update AJAX callback.
7002
		 *
7003
		 * @author Vova Feldman (@svovaf)
7004
		 * @since  1.2.1.5
7005
		 */
7006
		function _update_billing_ajax_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7007
			$this->_logger->entrance();
7008
7009
			$this->check_ajax_referer( 'update_billing' );
7010
7011
			if ( ! $this->is_user_admin() ) {
7012
				// Only for admins.
7013
				self::shoot_ajax_failure();
7014
			}
7015
7016
			$billing = fs_request_get( 'billing' );
7017
7018
			$api    = $this->get_api_user_scope();
7019
			$result = $api->call( '/billing.json', 'put', array_merge( $billing, array(
7020
				'plugin_id' => $this->get_parent_id(),
7021
			) ) );
7022
7023
			if ( ! $this->is_api_result_entity( $result ) ) {
7024
				self::shoot_ajax_failure();
7025
			}
7026
7027
			// Purge cached billing.
7028
			$this->get_api_user_scope()->purge_cache( 'billing.json' );
7029
7030
			self::shoot_ajax_success();
7031
		}
7032
7033
		/**
7034
		 * Trial start for anonymous users (AJAX callback).
7035
		 *
7036
		 * @author Vova Feldman (@svovaf)
7037
		 * @since  1.2.1.5
7038
		 */
7039
		function _start_trial_ajax_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7040
			$this->_logger->entrance();
7041
7042
			$this->check_ajax_referer( 'start_trial' );
7043
7044
			if ( ! $this->is_user_admin() ) {
7045
				// Only for admins.
7046
				self::shoot_ajax_failure();
7047
			}
7048
7049
			$trial_data = fs_request_get( 'trial' );
7050
7051
			$next_page = $this->opt_in(
7052
				false,
7053
				false,
7054
				false,
7055
				false,
7056
				false,
7057
				$trial_data['plan_id']
7058
			);
7059
7060
			if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) {
7061
				self::shoot_ajax_failure(
7062
					isset( $next_page->error ) ?
7063
						$next_page->error->message :
7064
						var_export( $next_page, true )
7065
				);
7066
			}
7067
7068
			$this->shoot_ajax_success( array(
7069
				'next_page' => $next_page,
7070
			) );
7071
		}
7072
7073
		/**
7074
		 * @author Leo Fajardo (@leorw)
7075
		 * @since  1.2.0
7076
		 */
7077
		function _resend_license_key_ajax_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7078
			$this->_logger->entrance();
7079
7080
			$this->check_ajax_referer( 'resend_license_key' );
7081
7082
			$email_address = sanitize_email( trim( fs_request_get( 'email', '', 'post' ) ) );
7083
7084
			if ( empty( $email_address ) ) {
7085
				exit;
7086
			}
7087
7088
			$error = false;
7089
7090
			$api    = $this->get_api_plugin_scope();
7091
			$result = $api->call( '/licenses/resend.json', 'post',
7092
				array(
7093
					'email' => $email_address,
7094
					'url'   => home_url(),
7095
				)
7096
			);
7097
7098
			if ( is_object( $result ) && isset( $result->error ) ) {
7099
				$error = $result->error;
7100
7101
				if ( in_array( $error->code, array( 'invalid_email', 'no_user' ) ) ) {
7102
					$error = $this->get_text( 'email-not-found' );
7103
				} else if ( 'no_license' === $error->code ) {
7104
					$error = $this->get_text( 'no-active-licenses' );
7105
				} else {
7106
					$error = $error->message;
7107
				}
7108
			}
7109
7110
			$licenses = array(
7111
				'success' => ( false === $error )
7112
			);
7113
7114
			if ( false !== $error ) {
7115
				$licenses['error'] = sprintf( '%s... %s', $this->get_text( 'oops' ), strtolower( $error ) );
7116
			}
7117
7118
			echo json_encode( $licenses );
7119
7120
			exit;
7121
		}
7122
7123
		/**
7124
		 * @author Vova Feldman (@svovaf)
7125
		 * @since  1.2.1.8
7126
		 *
7127
		 * @var string
7128
		 */
7129
		private static $_pagenow;
7130
7131
		/**
7132
		 * Get current page or the referer if executing a WP AJAX request.
7133
		 *
7134
		 * @author Vova Feldman (@svovaf)
7135
		 * @since  1.2.1.8
7136
		 *
7137
		 * @return string
7138
		 */
7139
		static function get_current_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7140
			if ( ! isset( self::$_pagenow ) ) {
7141
				global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
7142
7143
				self::$_pagenow = $pagenow;
7144
7145
				if ( self::is_ajax() &&
7146
				     'admin-ajax.php' === $pagenow
7147
				) {
7148
					$referer = wp_get_raw_referer();
7149
7150
					if ( is_string( $referer ) ) {
7151
						$parts = explode( '?', $referer );
7152
7153
						self::$_pagenow = basename( $parts[0] );
7154
					}
7155
				}
7156
			}
7157
7158
			return self::$_pagenow;
7159
		}
7160
7161
		/**
7162
		 * Helper method to check if user in the plugins page.
7163
		 *
7164
		 * @author Vova Feldman (@svovaf)
7165
		 * @since  1.2.1.5
7166
		 *
7167
		 * @return bool
7168
		 */
7169
		static function is_plugins_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7170
			return ( 'plugins.php' === self::get_current_page() );
7171
		}
7172
7173
		/**
7174
		 * Helper method to check if user in the themes page.
7175
		 *
7176
		 * @author Vova Feldman (@svovaf)
7177
		 * @since  1.2.2.6
7178
		 *
7179
		 * @return bool
7180
		 */
7181
		static function is_themes_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7182
			return ( 'themes.php' === self::get_current_page() );
7183
		}
7184
7185
		#----------------------------------------------------------------------------------
7186
		#region Affiliation
7187
		#----------------------------------------------------------------------------------
7188
7189
        /**
7190
         * @author Leo Fajardo
7191
         * @since 1.2.3
7192
         *
7193
         * @return bool
7194
         */
7195
        function has_affiliate_program() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7196
            if ( ! is_object( $this->_plugin ) ) {
7197
                return false;
7198
            }
7199
7200
		    return $this->_plugin->has_affiliate_program();
7201
        }
7202
7203
        /**
7204
         * @author Leo Fajardo (@leorw)
7205
         * @since 1.2.3
7206
         */
7207
        private function fetch_affiliate_and_terms() {
7208
            $this->_logger->entrance();
7209
7210
            if ( ! is_object( $this->plugin_affiliate_terms ) ) {
7211
                $plugins_api     = $this->get_api_plugin_scope();
7212
                $affiliate_terms = $plugins_api->get( '/aff.json?type=affiliation', true );
7213
7214
                if ( ! $this->is_api_result_entity( $affiliate_terms ) ) {
7215
                    return;
7216
                }
7217
7218
                $this->plugin_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms );
7219
            }
7220
7221
            if ( $this->is_registered() ) {
7222
                $users_api = $this->get_api_user_scope();
7223
                $result    = $users_api->get( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json", true );
7224
                if ( $this->is_api_result_object( $result, 'affiliates' ) ) {
7225
                    if ( ! empty( $result->affiliates ) ) {
7226
                        $affiliate = new FS_Affiliate( $result->affiliates[0] );
7227
7228
                        if ( ! $affiliate->is_pending() && ! empty( $this->_storage->affiliate_application_data ) ) {
0 ignored issues
show
Documentation introduced by
The property affiliate_application_data does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
7229
                            unset( $this->_storage->affiliate_application_data );
7230
                        }
7231
7232
                        if ( $affiliate->is_using_custom_terms ) {
7233
                            $affiliate_terms = $users_api->get( "/plugins/{$this->_plugin->id}/affiliates/{$affiliate->id}/aff/{$affiliate->custom_affiliate_terms_id}.json", true );
7234
                            if ( $this->is_api_result_entity( $affiliate_terms ) ) {
7235
                                $this->custom_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms );
7236
                            }
7237
                        }
7238
7239
                        $this->affiliate = $affiliate;
7240
                    }
7241
                }
7242
            }
7243
        }
7244
7245
        /**
7246
         * @author Leo Fajardo
7247
         * @since 1.2.3
7248
         *
7249
         * @return FS_Affiliate
7250
         */
7251
        function get_affiliate() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7252
            return $this->affiliate;
7253
        }
7254
7255
7256
        /**
7257
         * @author Leo Fajardo
7258
         * @since 1.2.3
7259
         *
7260
         * @return FS_AffiliateTerms
7261
         */
7262
        function get_affiliate_terms() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7263
            return is_object( $this->custom_affiliate_terms ) ?
7264
                $this->custom_affiliate_terms :
7265
                $this->plugin_affiliate_terms;
7266
        }
7267
7268
        /**
7269
         * @author Leo Fajardo
7270
         * @since 1.2.3
7271
         *
7272
         * @return FS_Affiliate|null
7273
         */
7274
        function _submit_affiliate_application() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7275
            $this->_logger->entrance();
7276
7277
            $this->check_ajax_referer( 'submit_affiliate_application' );
7278
7279
            if ( ! $this->is_user_admin() ) {
7280
                // Only for admins.
7281
                self::shoot_ajax_failure();
7282
            }
7283
7284
            $affiliate = fs_request_get( 'affiliate' );
7285
7286
            if ( empty( $affiliate['promotion_methods'] ) ) {
7287
                unset( $affiliate['promotion_methods'] );
7288
            }
7289
7290
            if ( ! empty( $affiliate['additional_domains'] ) ) {
7291
                $affiliate['additional_domains'] = array_unique( $affiliate['additional_domains'] );
7292
            }
7293
7294
            if ( ! $this->is_registered() ) {
7295
                // Opt in but don't track usage.
7296
                $next_page = $this->opt_in(
7297
                    false,
7298
                    false,
7299
                    false,
7300
                    false,
7301
                    false,
7302
                    false,
7303
                    true
7304
                );
7305
7306
                if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) {
7307
                    self::shoot_ajax_failure(
7308
                        isset( $next_page->error ) ?
7309
                            $next_page->error->message :
7310
                            var_export( $next_page, true )
7311
                    );
7312
                } else if ( $this->is_pending_activation() ) {
7313
                    self::shoot_ajax_failure( $this->get_text( 'account-is-pending-activation' ) );
7314
                }
7315
            }
7316
7317
            $api    = $this->get_api_user_scope();
7318
            $result = $api->call(
7319
                ( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json" ),
7320
                'post',
7321
                $affiliate
7322
            );
7323
7324
            if ( $this->is_api_error( $result ) ) {
7325
                self::shoot_ajax_failure(
7326
                    isset( $result->error ) ?
7327
                        $result->error->message :
7328
                        var_export( $result, true )
7329
                );
7330
            }
7331
            else
7332
            {
7333
                if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) {
7334
                    $this->_admin_notices->remove_sticky( 'affiliate_program' );
7335
                }
7336
7337
                $affiliate_application_data = array(
7338
                    'stats_description'            => $affiliate['stats_description'],
7339
                    'promotion_method_description' => $affiliate['promotion_method_description'],
7340
                );
7341
7342
                if ( ! empty( $affiliate['promotion_methods'] ) ) {
7343
                    $affiliate_application_data['promotion_methods'] = $affiliate['promotion_methods'];
7344
                }
7345
7346
                if ( ! empty( $affiliate['domain'] ) ) {
7347
                    $affiliate_application_data['domain'] = $affiliate['domain'];
7348
                }
7349
7350
                if ( ! empty( $affiliate['additional_domains'] ) ) {
7351
                    $affiliate_application_data['additional_domains'] = $affiliate['additional_domains'];
7352
                }
7353
7354
                $this->_storage->affiliate_application_data = $affiliate_application_data;
0 ignored issues
show
Documentation introduced by
The property affiliate_application_data does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
7355
            }
7356
7357
            // Purge cached affiliate.
7358
            $api->purge_cache( 'affiliate.json' );
7359
7360
            self::shoot_ajax_success( $result );
7361
        }
7362
7363
        /**
7364
         * @author Leo Fajardo
7365
         * @since 1.2.3
7366
         *
7367
         * @return array|null
7368
         */
7369
        function get_affiliate_application_data() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7370
            if ( empty( $this->_storage->affiliate_application_data ) ) {
0 ignored issues
show
Documentation introduced by
The property affiliate_application_data does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
7371
                return null;
7372
            }
7373
7374
            return $this->_storage->affiliate_application_data;
0 ignored issues
show
Documentation introduced by
The property affiliate_application_data does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
7375
        }
7376
7377
        #endregion Affiliation ------------------------------------------------------------
7378
7379
		#----------------------------------------------------------------------------------
7380
		#region URL Generators
7381
		#----------------------------------------------------------------------------------
7382
7383
		/**
7384
		 * Alias to pricing_url().
7385
		 *
7386
		 * @author Vova Feldman (@svovaf)
7387
		 * @since  1.0.2
7388
		 *
7389
		 * @uses   pricing_url()
7390
		 *
7391
		 * @param string $period Billing cycle
7392
		 * @param bool   $is_trial
7393
		 *
7394
		 * @return string
7395
		 */
7396
		function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7397
			return $this->pricing_url( $period, $is_trial );
7398
		}
7399
7400
		/**
7401
		 * @author Vova Feldman (@svovaf)
7402
		 * @since  1.0.9
7403
		 *
7404
		 * @uses   get_upgrade_url()
7405
		 *
7406
		 * @return string
7407
		 */
7408
		function get_trial_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7409
			return $this->get_upgrade_url( WP_FS__PERIOD_ANNUALLY, true );
7410
		}
7411
7412
		/**
7413
		 * Plugin's pricing URL.
7414
		 *
7415
		 * @author Vova Feldman (@svovaf)
7416
		 * @since  1.0.4
7417
		 *
7418
		 * @param string $billing_cycle Billing cycle
7419
		 *
7420
		 * @param bool   $is_trial
7421
		 *
7422
		 * @return string
7423
		 */
7424
		function pricing_url( $billing_cycle = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7425
			$this->_logger->entrance();
7426
7427
			$params = array(
7428
				'billing_cycle' => $billing_cycle
7429
			);
7430
7431
			if ( $is_trial ) {
7432
				$params['trial'] = 'true';
7433
			}
7434
7435
			return $this->_get_admin_page_url( 'pricing', $params );
7436
		}
7437
7438
		/**
7439
		 * Checkout page URL.
7440
		 *
7441
		 * @author   Vova Feldman (@svovaf)
7442
		 * @since    1.0.6
7443
		 *
7444
		 * @param string $billing_cycle Billing cycle
7445
		 * @param bool   $is_trial
7446
		 * @param array  $extra         (optional) Extra parameters, override other query params.
7447
		 *
7448
		 * @return string
7449
		 */
7450
		function checkout_url(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7451
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
7452
			$is_trial = false,
7453
			$extra = array()
7454
		) {
7455
			$this->_logger->entrance();
7456
7457
			$params = array(
7458
				'checkout'      => 'true',
7459
				'billing_cycle' => $billing_cycle,
7460
			);
7461
7462
			if ( $is_trial ) {
7463
				$params['trial'] = 'true';
7464
			}
7465
7466
			/**
7467
			 * Params in extra override other params.
7468
			 */
7469
			$params = array_merge( $params, $extra );
7470
7471
			return $this->_get_admin_page_url( 'pricing', $params );
7472
		}
7473
7474
		/**
7475
		 * Add-on checkout URL.
7476
		 *
7477
		 * @author   Vova Feldman (@svovaf)
7478
		 * @since    1.1.7
7479
		 *
7480
		 * @param number $addon_id
7481
		 * @param number $pricing_id
7482
		 * @param string $billing_cycle
7483
		 * @param bool   $is_trial
7484
		 *
7485
		 * @return string
7486
		 */
7487
		function addon_checkout_url(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7488
			$addon_id,
7489
			$pricing_id,
7490
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
7491
			$is_trial = false
7492
		) {
7493
			return $this->checkout_url( $billing_cycle, $is_trial, array(
7494
				'plugin_id'  => $addon_id,
7495
				'pricing_id' => $pricing_id,
7496
			) );
7497
		}
7498
7499
		#endregion
7500
7501
		#endregion ------------------------------------------------------------------
7502
7503
		/**
7504
		 * Check if plugin has any add-ons.
7505
		 *
7506
		 * @author Vova Feldman (@svovaf)
7507
		 * @since  1.0.5
7508
		 *
7509
		 * @since  1.1.7.3 Base logic only on the parameter provided by the developer in the init function.
7510
		 *
7511
		 * @return bool
7512
		 */
7513
		function has_addons() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7514
			$this->_logger->entrance();
7515
7516
			return $this->_has_addons;
7517
		}
7518
7519
		/**
7520
		 * Check if plugin can work in anonymous mode.
7521
		 *
7522
		 * @author     Vova Feldman (@svovaf)
7523
		 * @since      1.0.9
7524
		 *
7525
		 * @return bool
7526
		 *
7527
		 * @deprecated Please use is_enable_anonymous() instead
7528
		 */
7529
		function enable_anonymous() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7530
			return $this->_enable_anonymous;
7531
		}
7532
7533
		/**
7534
		 * Check if plugin can work in anonymous mode.
7535
		 *
7536
		 * @author Vova Feldman (@svovaf)
7537
		 * @since  1.1.9
7538
		 *
7539
		 * @return bool
7540
		 */
7541
		function is_enable_anonymous() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7542
			return $this->_enable_anonymous;
7543
		}
7544
7545
		/**
7546
		 * Check if plugin is premium only (no free plans).
7547
		 *
7548
		 * @author Vova Feldman (@svovaf)
7549
		 * @since  1.1.9
7550
		 *
7551
		 * @return bool
7552
		 */
7553
		function is_only_premium() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7554
			return $this->_is_premium_only;
7555
		}
7556
7557
		/**
7558
		 * Checks if the plugin's type is "plugin". The other type is "theme".
7559
		 *
7560
		 * @author Leo Fajardo (@leorw)
7561
		 * @since  1.2.2
7562
		 *
7563
		 * @return bool
7564
		 */
7565
		function is_plugin() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7566
			return ( WP_FS__MODULE_TYPE_PLUGIN === $this->_module_type );
7567
		}
7568
7569
		/**
7570
		 * @author Leo Fajardo (@leorw)
7571
		 * @since  1.2.2
7572
		 *
7573
		 * @return string
7574
		 */
7575
		function get_module_type() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7576
			if ( ! isset( $this->_module_type ) ) {
7577
				$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
7578
				$this->_module_type    = $id_slug_type_path_map[ $this->_module_id ]['type'];
7579
			}
7580
7581
			return $this->_module_type;
7582
		}
7583
7584
		/**
7585
		 * @author Leo Fajardo (@leorw)
7586
		 * @since  1.2.2
7587
		 *
7588
		 * @return string
7589
		 */
7590
		function get_plugin_main_file_path() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7591
			return $this->_plugin_main_file_path;
7592
		}
7593
7594
		/**
7595
		 * Check if module has a premium code version.
7596
		 *
7597
		 * Serviceware module might be freemium without any
7598
		 * premium code version, where the paid features
7599
		 * are all part of the service.
7600
		 *
7601
		 * @author Vova Feldman (@svovaf)
7602
		 * @since  1.2.1.6
7603
		 *
7604
		 * @return bool
7605
		 */
7606
		function has_premium_version() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7607
			return $this->_has_premium_version;
7608
		}
7609
7610
		/**
7611
		 * Check if feature supported with current site's plan.
7612
		 *
7613
		 * @author Vova Feldman (@svovaf)
7614
		 * @since  1.0.1
7615
		 *
7616
		 * @todo   IMPLEMENT
7617
		 *
7618
		 * @param number $feature_id
7619
		 *
7620
		 * @throws Exception
7621
		 */
7622
		function is_feature_supported( $feature_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $feature_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7623
			throw new Exception( 'not implemented' );
7624
		}
7625
7626
		/**
7627
		 * @author Vova Feldman (@svovaf)
7628
		 * @since  1.0.1
7629
		 *
7630
		 * @return bool Is running in SSL/HTTPS
7631
		 */
7632
		function is_ssl() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7633
			return WP_FS__IS_HTTPS;
7634
		}
7635
7636
		/**
7637
		 * @author Vova Feldman (@svovaf)
7638
		 * @since  1.0.9
7639
		 *
7640
		 * @return bool Is running in AJAX call.
7641
		 *
7642
		 * @link   http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax
7643
		 */
7644
		static function is_ajax() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7645
			return ( defined( 'DOING_AJAX' ) && DOING_AJAX );
7646
		}
7647
7648
		/**
7649
		 * Check if it's an AJAX call targeted for the current module.
7650
		 *
7651
		 * @author Vova Feldman (@svovaf)
7652
		 * @since  1.2.0
7653
		 *
7654
		 * @param array|string $actions Collection of AJAX actions.
7655
		 *
7656
		 * @return bool
7657
		 */
7658
		function is_ajax_action( $actions ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7659
			// Verify it's an ajax call.
7660
			if ( ! self::is_ajax() ) {
7661
				return false;
7662
			}
7663
7664
			// Verify the call is relevant for the plugin.
7665
			if ( $this->_module_id != fs_request_get( 'module_id' ) ) {
7666
				return false;
7667
			}
7668
7669
			// Verify it's one of the specified actions.
7670
			if ( is_string( $actions ) ) {
7671
				$actions = explode( ',', $actions );
7672
			}
7673
7674
			if ( is_array( $actions ) && 0 < count( $actions ) ) {
7675
				$ajax_action = fs_request_get( 'action' );
7676
7677
				foreach ( $actions as $action ) {
7678
					if ( $ajax_action === $this->get_action_tag( $action ) ) {
7679
						return true;
7680
					}
7681
				}
7682
			}
7683
7684
			return false;
7685
		}
7686
7687
		/**
7688
		 * Check if it's an AJAX call targeted for current request.
7689
		 *
7690
		 * @author Vova Feldman (@svovaf)
7691
		 * @since  1.2.0
7692
		 *
7693
		 * @param array|string $actions Collection of AJAX actions.
7694
		 * @param number|null  $module_id
7695
		 *
7696
		 * @return bool
7697
		 */
7698
		static function is_ajax_action_static( $actions, $module_id = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7699
			// Verify it's an ajax call.
7700
			if ( ! self::is_ajax() ) {
7701
				return false;
7702
			}
7703
7704
7705
			if ( ! empty( $module_id ) ) {
7706
				// Verify the call is relevant for the plugin.
7707
				if ( $module_id != fs_request_get( 'module_id' ) ) {
7708
					return false;
7709
				}
7710
			}
7711
7712
			// Verify it's one of the specified actions.
7713
			if ( is_string( $actions ) ) {
7714
				$actions = explode( ',', $actions );
7715
			}
7716
7717
			if ( is_array( $actions ) && 0 < count( $actions ) ) {
7718
				$ajax_action = fs_request_get( 'action' );
7719
7720
				foreach ( $actions as $action ) {
7721
					if ( $ajax_action === self::get_ajax_action_static( $action, $module_id ) ) {
7722
						return true;
7723
					}
7724
				}
7725
			}
7726
7727
			return false;
7728
		}
7729
7730
		/**
7731
		 * @author Vova Feldman (@svovaf)
7732
		 * @since  1.1.7
7733
		 *
7734
		 * @return bool
7735
		 */
7736
		static function is_cron() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7737
			return ( defined( 'DOING_CRON' ) && DOING_CRON );
7738
		}
7739
7740
		/**
7741
		 * Check if a real user is visiting the admin dashboard.
7742
		 *
7743
		 * @author Vova Feldman (@svovaf)
7744
		 * @since  1.1.7
7745
		 *
7746
		 * @return bool
7747
		 */
7748
		function is_user_in_admin() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7749
			return is_admin() && ! self::is_ajax() && ! self::is_cron();
7750
		}
7751
7752
		/**
7753
		 * Check if a real user is in the customizer view.
7754
		 *
7755
		 * @author Vova Feldman (@svovaf)
7756
		 * @since  1.2.2.7
7757
		 *
7758
		 * @return bool
7759
		 */
7760
		static function is_customizer() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7761
			return is_customize_preview();
7762
		}
7763
7764
		/**
7765
		 * Check if running in HTTPS and if site's plan matching the specified plan.
7766
		 *
7767
		 * @param string $plan
7768
		 * @param bool   $exact
7769
		 *
7770
		 * @return bool
7771
		 */
7772
		function is_ssl_and_plan( $plan, $exact = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7773
			return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) );
7774
		}
7775
7776
		/**
7777
		 * Construct plugin's settings page URL.
7778
		 *
7779
		 * @author Vova Feldman (@svovaf)
7780
		 * @since  1.0.4
7781
		 *
7782
		 * @param string $page
7783
		 * @param array  $params
7784
		 *
7785
		 * @return string
7786
		 */
7787
		function _get_admin_page_url( $page = '', $params = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7788
			if ( 0 < count( $params ) ) {
7789
				foreach ( $params as $k => $v ) {
7790
					$params[ $k ] = urlencode( $v );
7791
				}
7792
			}
7793
7794
			$page_param = $this->_menu->get_slug( $page );
7795
7796
            if ( empty( $page ) &&
7797
                $this->is_theme() &&
7798
                // Show the opt-in as an overlay for free wp.org themes or themes without any settings page.
7799
                ( $this->is_free_wp_org_theme() || ! $this->has_settings_menu() ) ) {
7800
                    $params[ $this->get_unique_affix() . '_show_optin' ] = 'true';
7801
7802
                    return add_query_arg(
7803
                        $params,
7804
                        admin_url( 'themes.php' )
7805
                    );
7806
            }
7807
7808
			if ( ! $this->has_settings_menu() ) {
7809
				if ( ! empty( $page ) ) {
7810
					// Module doesn't have a setting page, but since the request is for
7811
					// a specific Freemius page, use the admin.php path.
7812
					return add_query_arg( array_merge( $params, array(
7813
						'page' => $page_param,
7814
					) ), admin_url( 'admin.php' ) );
7815
				} else {
7816
					if ( $this->is_activation_mode() ) {
7817
						/**
7818
						 * @author Vova Feldman
7819
						 * @since  1.2.1.6
7820
						 *
7821
						 * If plugin doesn't have a settings page, create one for the opt-in screen.
7822
						 */
7823
						return add_query_arg( array_merge( $params, array(
7824
							'page' => $this->_slug,
7825
						) ), admin_url( 'admin.php', 'admin' ) );
7826
					} else {
7827
						// Plugin without a settings page.
7828
                        return add_query_arg(
7829
                            $params,
7830
                            admin_url( 'plugins.php' )
7831
                        );
7832
					}
7833
				}
7834
			}
7835
7836
			// Module has a submenu settings page.
7837
			if ( ! $this->_menu->is_top_level() ) {
7838
				$parent_slug = $this->_menu->get_parent_slug();
7839
				$menu_file   = ( false !== strpos( $parent_slug, '.php' ) ) ?
7840
					$parent_slug :
7841
					'admin.php';
7842
7843
				return add_query_arg( array_merge( $params, array(
7844
					'page' => $page_param,
7845
				) ), admin_url( $menu_file, 'admin' ) );
7846
			}
7847
7848
			// Module has a top level CPT settings page.
7849
			if ( $this->_menu->is_cpt() ) {
7850
				if ( empty( $page ) && $this->is_activation_mode() ) {
7851
					return add_query_arg( array_merge( $params, array(
7852
						'page' => $page_param
7853
					) ), admin_url( 'admin.php', 'admin' ) );
7854
				} else {
7855
					if ( ! empty( $page ) ) {
7856
						$params['page'] = $page_param;
7857
					}
7858
7859
					return add_query_arg(
7860
						$params,
7861
						admin_url( $this->_menu->get_raw_slug(), 'admin' )
7862
					);
7863
				}
7864
			}
7865
7866
			// Module has a custom top level settings page.
7867
			return add_query_arg( array_merge( $params, array(
7868
				'page' => $page_param,
7869
			) ), admin_url( 'admin.php', 'admin' ) );
7870
		}
7871
7872
		/**
7873
		 * Check if currently in a specified admin page.
7874
		 *
7875
		 * @author Vova Feldman (@svovaf)
7876
		 * @since  1.2.2.7
7877
		 *
7878
		 * @param string $page
7879
		 *
7880
		 * @return bool
7881
		 */
7882
		function is_admin_page( $page ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7883
			return ( $this->_menu->get_slug( $page ) === fs_request_get( 'page', '', 'get' ) );
7884
		}
7885
7886
		/**
7887
		 * Get module's main admin setting page URL.
7888
		 *
7889
		 * @author Vova Feldman (@svovaf)
7890
		 * @since  1.2.2.7
7891
		 *
7892
		 * @return string
7893
		 */
7894
		function main_menu_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7895
			return $this->_menu->main_menu_url();
7896
		}
7897
7898
		/**
7899
		 * Check if currently on the theme's setting page or
7900
		 * on any of the Freemius added pages (via tabs).
7901
		 *
7902
		 * @author Vova Feldman (@svovaf)
7903
		 * @since  1.2.2.7
7904
		 *
7905
		 * @return bool
7906
		 */
7907
		function is_theme_settings_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7908
			return fs_starts_with(
7909
				fs_request_get( 'page', '', 'get' ),
7910
				$this->_menu->get_slug()
7911
			);
7912
		}
7913
7914
		/**
7915
		 * Plugin's account page + sync license URL.
7916
		 *
7917
		 * @author Vova Feldman (@svovaf)
7918
		 * @since  1.1.9.1
7919
		 *
7920
		 * @param bool|number $plugin_id
7921
		 * @param bool        $add_action_nonce
7922
		 * @param array       $params
7923
		 *
7924
		 * @return string
7925
		 */
7926
		function _get_sync_license_url( $plugin_id = false, $add_action_nonce = true, $params = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7927
			if ( is_numeric( $plugin_id ) ) {
7928
				$params['plugin_id'] = $plugin_id;
7929
			}
7930
7931
			return $this->get_account_url(
7932
				$this->get_unique_affix() . '_sync_license',
7933
				$params,
7934
				$add_action_nonce
7935
			);
7936
		}
7937
7938
		/**
7939
		 * Plugin's account URL.
7940
		 *
7941
		 * @author Vova Feldman (@svovaf)
7942
		 * @since  1.0.4
7943
		 *
7944
		 * @param bool|string $action
7945
		 * @param array       $params
7946
		 *
7947
		 * @param bool        $add_action_nonce
7948
		 *
7949
		 * @return string
7950
		 */
7951
		function get_account_url( $action = false, $params = array(), $add_action_nonce = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7952
			if ( is_string( $action ) ) {
7953
				$params['fs_action'] = $action;
7954
			}
7955
7956
			self::require_pluggable_essentials();
7957
7958
			return ( $add_action_nonce && is_string( $action ) ) ?
7959
				fs_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) :
7960
				$this->_get_admin_page_url( 'account', $params );
7961
		}
7962
7963
		/**
7964
		 * @author  Vova Feldman (@svovaf)
7965
		 * @since   1.2.0
7966
		 *
7967
		 * @param string $tab
7968
		 * @param bool   $action
7969
		 * @param array  $params
7970
		 * @param bool   $add_action_nonce
7971
		 *
7972
		 * @return string
7973
		 *
7974
		 * @uses    get_account_url()
7975
		 */
7976
		function get_account_tab_url( $tab, $action = false, $params = array(), $add_action_nonce = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7977
			$params['tab'] = $tab;
7978
7979
			return $this->get_account_url( $action, $params, $add_action_nonce );
7980
		}
7981
7982
		/**
7983
		 * Plugin's account URL.
7984
		 *
7985
		 * @author Vova Feldman (@svovaf)
7986
		 * @since  1.0.4
7987
		 *
7988
		 * @param bool|string $topic
7989
		 * @param bool|string $message
7990
		 *
7991
		 * @return string
7992
		 */
7993
		function contact_url( $topic = false, $message = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7994
			$params = array();
7995
			if ( is_string( $topic ) ) {
7996
				$params['topic'] = $topic;
7997
			}
7998
			if ( is_string( $message ) ) {
7999
				$params['message'] = $message;
8000
			}
8001
8002
			if ( $this->is_addon() ) {
8003
				$params['addon_id'] = $this->get_id();
8004
8005
				return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params );
8006
			} else {
8007
				return $this->_get_admin_page_url( 'contact', $params );
8008
			}
8009
		}
8010
8011
		/**
8012
		 * Add-on direct info URL.
8013
		 *
8014
		 * @author Vova Feldman (@svovaf)
8015
		 * @since  1.1.0
8016
		 *
8017
		 * @param string $slug
8018
		 *
8019
		 * @return string
8020
		 */
8021
		function addon_url( $slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8022
			return $this->_get_admin_page_url( 'addons', array(
8023
				'slug' => $slug
8024
			) );
8025
		}
8026
8027
		/* Logger
8028
		------------------------------------------------------------------------------------------------------------------*/
8029
		/**
8030
		 * @param string $id
8031
		 * @param bool   $prefix_slug
8032
		 *
8033
		 * @return FS_Logger
8034
		 */
8035
		function get_logger( $id = '', $prefix_slug = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8036
			return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id );
8037
		}
8038
8039
		/**
8040
		 * @param      $id
8041
		 * @param bool $load_options
8042
		 * @param bool $prefix_slug
8043
		 *
8044
		 * @return FS_Option_Manager
8045
		 */
8046
		function get_options_manager( $id, $load_options = false, $prefix_slug = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8047
			return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options );
8048
		}
8049
8050
		/* Security
8051
		------------------------------------------------------------------------------------------------------------------*/
8052
		private static function _encrypt( $str ) {
8053
			if ( is_null( $str ) ) {
8054
				return null;
8055
			}
8056
8057
			/**
8058
			 * The encrypt/decrypt functions are used to protect
8059
			 * the user from messing up with some of the sensitive
8060
			 * data stored for the module as a JSON in the database.
8061
			 *
8062
			 * I used the same suggested hack by the theme review team.
8063
			 * For more details, look at the function `Base64UrlDecode()`
8064
			 * in `./sdk/FreemiusBase.php`.
8065
			 *
8066
			 * @todo   Remove this hack once the base64 error is removed from the Theme Check.
8067
			 *
8068
			 * @author Vova Feldman (@svovaf)
8069
			 * @since  1.2.2
8070
			 */
8071
			$fn = 'base64' . '_encode';
8072
8073
			return $fn( $str );
8074
		}
8075
8076
		static function _decrypt( $str ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8077
			if ( is_null( $str ) ) {
8078
				return null;
8079
			}
8080
8081
			/**
8082
			 * The encrypt/decrypt functions are used to protect
8083
			 * the user from messing up with some of the sensitive
8084
			 * data stored for the module as a JSON in the database.
8085
			 *
8086
			 * I used the same suggested hack by the theme review team.
8087
			 * For more details, look at the function `Base64UrlDecode()`
8088
			 * in `./sdk/FreemiusBase.php`.
8089
			 *
8090
			 * @todo   Remove this hack once the base64 error is removed from the Theme Check.
8091
			 *
8092
			 * @author Vova Feldman (@svovaf)
8093
			 * @since  1.2.2
8094
			 */
8095
			$fn = 'base64' . '_decode';
8096
8097
			return $fn( $str );
8098
		}
8099
8100
		/**
8101
		 * @author Vova Feldman (@svovaf)
8102
		 * @since  1.0.5
8103
		 *
8104
		 * @param FS_Entity $entity
8105
		 *
8106
		 * @return FS_Entity Return an encrypted clone entity.
8107
		 */
8108
		private static function _encrypt_entity( FS_Entity $entity ) {
8109
			$clone = clone $entity;
8110
			$props = get_object_vars( $entity );
8111
8112
			foreach ( $props as $key => $val ) {
8113
				$clone->{$key} = self::_encrypt( $val );
8114
			}
8115
8116
			return $clone;
8117
		}
8118
8119
		/**
8120
		 * @author Vova Feldman (@svovaf)
8121
		 * @since  1.0.5
8122
		 *
8123
		 * @param FS_Entity $entity
8124
		 *
8125
		 * @return FS_Entity Return an decrypted clone entity.
8126
		 */
8127
		private static function decrypt_entity( FS_Entity $entity ) {
8128
			$clone = clone $entity;
8129
			$props = get_object_vars( $entity );
8130
8131
			foreach ( $props as $key => $val ) {
8132
				$clone->{$key} = self::_decrypt( $val );
8133
			}
8134
8135
			return $clone;
8136
		}
8137
8138
		/**
8139
		 * Tries to activate account based on POST params.
8140
		 *
8141
		 * @author Vova Feldman (@svovaf)
8142
		 * @since  1.0.2
8143
		 */
8144
		function _activate_account() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8145
			if ( $this->is_registered() ) {
8146
				// Already activated.
8147
				return;
8148
			}
8149
8150
			self::_clean_admin_content_section();
8151
8152
			if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) {
8153
//				check_admin_referer( 'activate_' . $this->_plugin->public_key );
0 ignored issues
show
Unused Code Comprehensibility introduced by
44% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
8154
8155
				// Verify matching plugin details.
8156
				if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) {
8157
					return;
8158
				}
8159
8160
				$user              = new FS_User();
8161
				$user->id          = fs_request_get( 'user_id' );
8162
				$user->public_key  = fs_request_get( 'user_public_key' );
8163
				$user->secret_key  = fs_request_get( 'user_secret_key' );
8164
				$user->email       = fs_request_get( 'user_email' );
8165
				$user->first       = fs_request_get( 'user_first' );
8166
				$user->last        = fs_request_get( 'user_last' );
8167
				$user->is_verified = fs_request_get_bool( 'user_is_verified' );
8168
8169
				$site              = new FS_Site();
8170
				$site->id          = fs_request_get( 'install_id' );
8171
				$site->public_key  = fs_request_get( 'install_public_key' );
8172
				$site->secret_key  = fs_request_get( 'install_secret_key' );
8173
				$site->plan->id    = fs_request_get( 'plan_id' );
8174
				$site->plan->title = fs_request_get( 'plan_title' );
8175
				$site->plan->name  = fs_request_get( 'plan_name' );
8176
8177
				$plans      = array();
8178
				$plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) );
8179
				foreach ( $plans_data as $p ) {
8180
					$plans[] = new FS_Plugin_Plan( $p );
8181
				}
8182
8183
				$this->_set_account( $user, $site, $plans );
8184
8185
				// Reload the page with the keys.
8186
				fs_redirect( $this->_get_admin_page_url() );
8187
			}
8188
		}
8189
8190
		/**
8191
		 * @author Vova Feldman (@svovaf)
8192
		 * @since  1.0.7
8193
		 *
8194
		 * @param string $email
8195
		 *
8196
		 * @return FS_User|bool
8197
		 */
8198
		static function _get_user_by_email( $email ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8199
			self::$_static_logger->entrance();
8200
8201
			$email = trim( strtolower( $email ) );
8202
			$users = self::get_all_users();
8203
			if ( is_array( $users ) ) {
8204
				foreach ( $users as $u ) {
8205
					if ( $email === trim( strtolower( $u->email ) ) ) {
8206
						return $u;
8207
					}
8208
				}
8209
			}
8210
8211
			return false;
8212
		}
8213
8214
		#----------------------------------------------------------------------------------
8215
		#region Account (Loading, Updates & Activation)
8216
		#----------------------------------------------------------------------------------
8217
8218
		/***
8219
		 * Load account information (user + site).
8220
		 *
8221
		 * @author Vova Feldman (@svovaf)
8222
		 * @since  1.0.1
8223
		 */
8224
		private function _load_account() {
8225
			$this->_logger->entrance();
8226
8227
			$this->do_action( 'before_account_load' );
8228
8229
			$sites    = self::get_all_sites( $this->_module_type );
8230
			$users    = self::get_all_users();
8231
			$plans    = self::get_all_plans( $this->_module_type );
8232
			$licenses = self::get_all_licenses( $this->_module_type );
8233
8234
			if ( $this->_logger->is_on() && is_admin() ) {
8235
				$this->_logger->log( 'sites = ' . var_export( $sites, true ) );
8236
				$this->_logger->log( 'users = ' . var_export( $users, true ) );
8237
				$this->_logger->log( 'plans = ' . var_export( $plans, true ) );
8238
				$this->_logger->log( 'licenses = ' . var_export( $licenses, true ) );
8239
			}
8240
8241
			$site = isset( $sites[ $this->_slug ] ) ? $sites[ $this->_slug ] : false;
8242
8243
			if ( is_object( $site ) &&
8244
			     is_numeric( $site->id ) &&
8245
			     is_numeric( $site->user_id ) &&
8246
			     is_object( $site->plan )
8247
			) {
8248
				// Load site.
8249
				$this->_site       = clone $site;
8250
				$this->_site->plan = self::decrypt_entity( $this->_site->plan );
8251
8252
                /**
8253
                 * If the install owner's details are not stored locally, use the previous user's details if available.
8254
                 *
8255
                 * @author Leo Fajardo (@leorw)
8256
                 */
8257
				if ( ! isset( $users[ $this->_site->user_id ] ) && FS_User::is_valid_id( $this->_storage->prev_user_id ) ) {
0 ignored issues
show
Documentation introduced by
The property prev_user_id does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
8258
                    $user_id = $this->_storage->prev_user_id;
0 ignored issues
show
Documentation introduced by
The property prev_user_id does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
8259
                } else {
8260
                    $user_id = $this->_site->user_id;
8261
                }
8262
8263
				// Load relevant user.
8264
				$this->_user = clone $users[ $user_id ];
8265
8266
				// Load plans.
8267
				$this->_plans = $plans[ $this->_slug ];
0 ignored issues
show
Documentation Bug introduced by
It seems like $plans[$this->_slug] of type object<FS_Plugin_Plan> is incompatible with the declared type array<integer,object<FS_Plugin_Plan>> of property $_plans.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
8268
				if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) {
8269
					$this->_sync_plans();
8270
				} else {
8271
					for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
8272
						if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) {
8273
							$this->_plans[ $i ] = self::decrypt_entity( $this->_plans[ $i ] );
8274
						} else {
8275
							unset( $this->_plans[ $i ] );
8276
						}
8277
					}
8278
				}
8279
8280
				// Load licenses.
8281
				$this->_licenses = array();
8282
				if ( is_array( $licenses ) &&
8283
				     isset( $licenses[ $this->_slug ] ) &&
8284
				     isset( $licenses[ $this->_slug ][ $this->_user->id ] )
8285
				) {
8286
					$this->_licenses = $licenses[ $this->_slug ][ $this->_user->id ];
8287
				}
8288
8289
				$this->_license = $this->_get_license_by_id( $this->_site->license_id );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->_get_license_by_i...his->_site->license_id) can also be of type false. However, the property $_license is declared as type object<FS_Plugin_License>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
8290
8291
				if ( $this->_site->version != $this->get_plugin_version() ) {
8292
					// If stored install version is different than current installed plugin version,
8293
					// then update plugin version event.
8294
					$this->update_plugin_version_event();
8295
				}
8296
			}
8297
8298
			$this->_register_account_hooks();
8299
		}
8300
8301
		/**
8302
		 * @author Vova Feldman (@svovaf)
8303
		 * @since  1.0.1
8304
		 *
8305
		 * @param FS_User    $user
8306
		 * @param FS_Site    $site
8307
		 * @param bool|array $plans
8308
		 */
8309
		private function _set_account( FS_User $user, FS_Site $site, $plans = false ) {
8310
			$site->slug    = $this->_slug;
8311
			$site->user_id = $user->id;
8312
8313
			$this->_site = $site;
8314
			$this->_user = $user;
8315
			if ( false !== $plans ) {
8316
				$this->_plans = $plans;
0 ignored issues
show
Documentation Bug introduced by
It seems like $plans can also be of type boolean. However, the property $_plans is declared as type array<integer,object<FS_Plugin_Plan>>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
8317
			}
8318
8319
			$this->send_install_update();
8320
8321
			$this->_store_account();
8322
8323
		}
8324
8325
		/**
8326
		 * @author Vova Feldman (@svovaf)
8327
		 * @since  1.1.7.4
8328
		 *
8329
		 * @param array $override_with
8330
		 *
8331
		 * @return array
8332
		 */
8333
		function get_opt_in_params( $override_with = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8334
			$this->_logger->entrance();
8335
8336
			$current_user = self::_get_current_wp_user();
8337
8338
			$activation_action = $this->get_unique_affix() . '_activate_new';
8339
			$return_url        = $this->is_anonymous() ?
8340
				// If skipped already, then return to the account page.
8341
				$this->get_account_url( $activation_action, array(), false ) :
8342
				// Return to the module's main page.
8343
				$this->get_after_activation_url( 'after_connect_url', array( 'fs_action' => $activation_action ) );
8344
8345
			$params = array(
8346
				'user_firstname'               => $current_user->user_firstname,
8347
				'user_lastname'                => $current_user->user_lastname,
8348
				'user_nickname'                => $current_user->user_nicename,
8349
				'user_email'                   => $current_user->user_email,
8350
				'user_ip'                      => WP_FS__REMOTE_ADDR,
8351
				'plugin_slug'                  => $this->_slug,
8352
				'plugin_id'                    => $this->get_id(),
8353
				'plugin_public_key'            => $this->get_public_key(),
8354
				'plugin_version'               => $this->get_plugin_version(),
8355
				'return_url'                   => fs_nonce_url( $return_url, $activation_action ),
8356
				'account_url'                  => fs_nonce_url( $this->_get_admin_page_url(
8357
					'account',
8358
					array( 'fs_action' => 'sync_user' )
8359
				), 'sync_user' ),
8360
				'site_uid'                     => $this->get_anonymous_id(),
8361
				'site_url'                     => get_site_url(),
8362
				'site_name'                    => get_bloginfo( 'name' ),
8363
				'platform_version'             => get_bloginfo( 'version' ),
8364
				'sdk_version'                  => $this->version,
8365
				'programming_language_version' => phpversion(),
8366
				'language'                     => get_bloginfo( 'language' ),
8367
				'charset'                      => get_bloginfo( 'charset' ),
8368
				'is_premium'                   => $this->is_premium(),
8369
				'is_active'                    => true,
8370
				'is_uninstalled'               => false,
8371
			);
8372
8373
			if ( $this->is_pending_activation() &&
8374
			     ! empty( $this->_storage->pending_license_key )
0 ignored issues
show
Documentation introduced by
The property pending_license_key does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
8375
			) {
8376
				$params['license_key'] = $this->_storage->pending_license_key;
0 ignored issues
show
Documentation introduced by
The property pending_license_key does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
8377
			}
8378
8379
			if ( WP_FS__SKIP_EMAIL_ACTIVATION && $this->has_secret_key() ) {
8380
				// Even though rand() is known for its security issues,
8381
				// the timestamp adds another layer of protection.
8382
				// It would be very hard for an attacker to get the secret key form here.
8383
				// Plus, this should never run in production since the secret should never
8384
				// be included in the production version.
8385
				$params['ts']     = WP_FS__SCRIPT_START_TIME;
8386
				$params['salt']   = md5( uniqid( rand() ) );
8387
				$params['secure'] = md5(
8388
					$params['ts'] .
8389
					$params['salt'] .
8390
					$this->get_secret_key()
8391
				);
8392
			}
8393
8394
			return array_merge( $params, $override_with );
8395
		}
8396
8397
		/**
8398
		 * 1. If successful opt-in or pending activation returns the next page that the user should be redirected to.
8399
		 * 2. If there was an API error, return the API result.
8400
		 *
8401
		 * @author Vova Feldman (@svovaf)
8402
		 * @since  1.1.7.4
8403
		 *
8404
		 * @param string|bool $email
8405
		 * @param string|bool $first
8406
		 * @param string|bool $last
8407
		 * @param string|bool $license_key
8408
		 * @param bool        $is_uninstall       If "true", this means that the module is currently being uninstalled.
8409
		 *                                        In this case, the user and site info will be sent to the server but no
8410
		 *                                        data will be saved to the WP installation's database.
8411
		 * @param number|bool $trial_plan_id
8412
		 * @param bool        $is_disconnected Whether or not to opt in without tracking.
8413
		 *
8414
		 * @return string|object
8415
		 * @use    WP_Error
8416
		 */
8417
		function opt_in(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
Coding Style introduced by
opt_in uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
8418
			$email = false,
8419
			$first = false,
8420
			$last = false,
8421
			$license_key = false,
8422
			$is_uninstall = false,
8423
			$trial_plan_id = false,
8424
            $is_disconnected = false
8425
		) {
8426
			$this->_logger->entrance();
8427
8428
			if ( false === $email ) {
8429
				$current_user = self::_get_current_wp_user();
8430
				$email        = $current_user->user_email;
8431
			}
8432
8433
			/**
8434
			 * @since 1.2.1 If activating with license key, ignore the context-user
8435
			 *              since the user will be automatically loaded from the license.
8436
			 */
8437
			if ( empty( $license_key ) ) {
8438
				// Clean up pending license if opt-ing in again.
8439
				$this->_storage->remove( 'pending_license_key' );
8440
8441
				if ( ! $is_uninstall ) {
8442
					$fs_user = Freemius::_get_user_by_email( $email );
8443
					if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) {
8444
						return $this->install_with_current_user( false, $trial_plan_id );
8445
					}
8446
				}
8447
			}
8448
8449
			$user_info = array();
8450
			if ( ! empty( $email ) ) {
8451
				$user_info['user_email'] = $email;
8452
			}
8453
			if ( ! empty( $first ) ) {
8454
				$user_info['user_firstname'] = $first;
8455
			}
8456
			if ( ! empty( $last ) ) {
8457
				$user_info['user_lastname'] = $last;
8458
			}
8459
8460
			$params = $this->get_opt_in_params( $user_info );
8461
8462
			$filtered_license_key = false;
8463
			if ( is_string( $license_key ) ) {
8464
				$filtered_license_key  = $this->apply_filters( 'license_key', $license_key );
8465
				$params['license_key'] = $filtered_license_key;
8466
			} else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) {
8467
				$params['trial_plan_id'] = $trial_plan_id;
8468
			}
8469
8470
			if ( $is_uninstall ) {
8471
				$params['uninstall_params'] = array(
8472
					'reason_id'   => $this->_storage->uninstall_reason->id,
8473
					'reason_info' => $this->_storage->uninstall_reason->info
8474
				);
8475
			}
8476
8477
			if ( isset( $params['license_key'] ) ) {
8478
				$fs_user = Freemius::_get_user_by_email( $email );
8479
8480
				if ( is_object( $fs_user ) ) {
8481
					/**
8482
					 * If opting in with a context license and the context WP Admin user already opted in
8483
					 * before from the current site, add the user context security params to avoid the
8484
					 * unnecessry email activation when the context license is owned by the same context user.
8485
					 * 
8486
					 * @author Leo Fajardo (@leorw)
8487
					 * @since 1.2.3
8488
					 */
8489
					$params = array_merge( $params, FS_Security::instance()->get_context_params(
8490
						$fs_user,
8491
						false,
8492
						'install_with_existing_user'
8493
					) );
8494
				}
8495
			}
8496
8497
            $params['is_disconnected'] = $is_disconnected;
8498
			$params['format']          = 'json';
8499
8500
			$url = WP_FS__ADDRESS . '/action/service/user/install/';
8501
			if ( isset( $_COOKIE['XDEBUG_SESSION'] ) ) {
8502
				$url = add_query_arg( 'XDEBUG_SESSION', 'PHPSTORM', $url );
8503
			}
8504
8505
			$response = wp_remote_post( $url, array(
8506
				'method'  => 'POST',
8507
				'body'    => $params,
8508
				'timeout' => 15,
8509
			) );
8510
8511
			if ( $response instanceof WP_Error ) {
0 ignored issues
show
Bug introduced by
The class WP_Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
8512
				if ( 'https://' === substr( $url, 0, 8 ) &&
8513
				     isset( $response->errors ) &&
8514
				     isset( $response->errors['http_request_failed'] )
8515
				) {
8516
					$http_error = strtolower( $response->errors['http_request_failed'][0] );
8517
8518
					if ( false !== strpos( $http_error, 'ssl' ) ) {
8519
						// Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare).
8520
						$url = 'http://' . substr( $url, 8 );
8521
8522
						$response = wp_remote_post( $url, array(
8523
							'method'  => 'POST',
8524
							'body'    => $params,
8525
							'timeout' => 15,
8526
						) );
8527
					}
8528
				}
8529
			}
8530
8531
			if ( is_wp_error( $response ) ) {
8532
				/**
8533
				 * @var WP_Error $response
8534
				 */
8535
				$result = new stdClass();
8536
8537
				$error_code = $response->get_error_code();
8538
				$error_type = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $error_code ) ) );
8539
8540
				$result->error = (object) array(
8541
					'type'    => $error_type,
8542
					'message' => $response->get_error_message(),
8543
					'code'    => $error_code,
8544
					'http'    => 402
8545
				);
8546
8547
				return $result;
8548
			}
8549
8550
			// Module is being uninstalled, don't handle the returned data.
8551
			if ( $is_uninstall ) {
8552
				return true;
8553
			}
8554
8555
			$decoded = @json_decode( $response['body'] );
8556
8557
			if ( empty( $decoded ) ) {
8558
				return false;
8559
			}
8560
8561
			if ( ! $this->is_api_result_object( $decoded ) ) {
8562
				if ( ! empty( $params['license_key'] ) ) {
8563
					// Pass the fully entered license key to the failure handler.
8564
					$params['license_key'] = $license_key;
8565
				}
8566
8567
				return $is_uninstall ?
8568
					$decoded :
8569
					$this->apply_filters( 'after_install_failure', $decoded, $params );
8570
			} else if ( isset( $decoded->pending_activation ) && $decoded->pending_activation ) {
8571
				// Pending activation, add message.
8572
				return $this->set_pending_confirmation(
8573
                    ( isset( $decoded->email ) ?
8574
                        $decoded->email :
8575
                        true ),
8576
					false,
8577
					$filtered_license_key,
8578
					! empty( $params['trial_plan_id'] )
8579
				);
8580
			} else if ( isset( $decoded->install_secret_key ) ) {
8581
				return $this->install_with_new_user(
8582
					$decoded->user_id,
8583
					$decoded->user_public_key,
8584
					$decoded->user_secret_key,
8585
					$decoded->install_id,
8586
					$decoded->install_public_key,
8587
					$decoded->install_secret_key,
8588
					false
8589
				);
8590
			}
8591
8592
			return $decoded;
8593
		}
8594
8595
		/**
8596
		 * Set user and site identities.
8597
		 *
8598
		 * @author Vova Feldman (@svovaf)
8599
		 * @since  1.0.9
8600
		 *
8601
		 * @param FS_User $user
8602
		 * @param FS_Site $site
8603
		 * @param bool    $redirect
8604
		 * @param bool    $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will
8605
		 *                              redirect (or return a URL) to the account page with a special parameter to
8606
		 *                              trigger the auto installation processes.
8607
		 *
8608
		 * @return string If redirect is `false`, returns the next page the user should be redirected to.
8609
		 */
8610
		function setup_account(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8611
			FS_User $user,
8612
			FS_Site $site,
8613
			$redirect = true,
8614
			$auto_install = false
8615
		) {
8616
			$this->_user = $user;
8617
			$this->_site = $site;
8618
8619
			$this->_sync_plans();
8620
8621
			$this->_enrich_site_plan( false );
8622
8623
			$this->_set_account( $user, $site );
8624
8625
			if ( $this->is_trial() ) {
8626
				// Store trial plan information.
8627
				$this->_enrich_site_trial_plan( true );
8628
			}
8629
8630
			// If Freemius was OFF before, turn it on.
8631
			$this->turn_on();
8632
8633
			$this->do_action( 'after_account_connection', $user, $site );
8634
8635
			if ( is_numeric( $site->license_id ) ) {
8636
				$this->_license = $this->_get_license_by_id( $site->license_id );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->_get_license_by_id($site->license_id) can also be of type false. However, the property $_license is declared as type object<FS_Plugin_License>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
8637
			}
8638
8639
			$this->_admin_notices->remove_sticky( 'connect_account' );
8640
8641
			if ( $this->is_pending_activation() || ! $this->has_settings_menu() ) {
8642
				// Remove pending activation sticky notice (if still exist).
8643
				$this->_admin_notices->remove_sticky( 'activation_pending' );
8644
8645
				// Remove plugin from pending activation mode.
8646
				unset( $this->_storage->is_pending_activation );
8647
8648
				if ( ! $this->is_paying_or_trial() ) {
8649
					$this->_admin_notices->add_sticky(
8650
						sprintf( $this->get_text( 'plugin-x-activation-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
8651
						'activation_complete'
8652
					);
8653
				}
8654
			}
8655
8656
			if ( $this->is_paying_or_trial() ) {
8657
				if ( ! $this->is_premium() || ! $this->has_premium_version() || ! $this->has_settings_menu() ) {
8658
					if ( $this->is_paying() ) {
8659
						$this->_admin_notices->add_sticky(
8660
							sprintf(
8661
								$this->get_text( 'activation-with-plan-x-message' ),
8662
								$this->_site->plan->title
8663
							) . $this->get_complete_upgrade_instructions(),
8664
							'plan_upgraded',
8665
							$this->get_text( 'yee-haw' ) . '!'
8666
						);
8667
					} else {
8668
						$this->_admin_notices->add_sticky(
8669
							sprintf(
8670
								$this->get_text( 'trial-started-message' ),
8671
								'<i>' . $this->get_plugin_name() . '</i>'
8672
							) . $this->get_complete_upgrade_instructions( $this->_storage->trial_plan->title ),
8673
							'trial_started',
8674
							$this->get_text( 'yee-haw' ) . '!'
8675
						);
8676
					}
8677
				}
8678
8679
				$this->_admin_notices->remove_sticky( array(
8680
					'trial_promotion',
8681
				) );
8682
			}
8683
8684
			$plugin_id = fs_request_get( 'plugin_id', false );
8685
8686
			// Store activation time ONLY for plugins (not add-ons).
8687
			if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) {
8688
				$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
8689
			}
8690
8691
			$next_page = '';
8692
8693
			$extra = array();
8694
			if ( $auto_install ) {
8695
				$extra['auto_install'] = 'true';
8696
			}
8697
8698
			if ( is_numeric( $plugin_id ) ) {
8699
				/**
8700
				 * @author Leo Fajardo
8701
				 * @since  1.2.1.6
8702
				 *
8703
				 * Also sync the license after an anonymous user subscribes.
8704
				 */
8705
				if ( $this->is_anonymous() || $plugin_id != $this->_plugin->id ) {
8706
					// Add-on was installed - sync license right after install.
8707
					$next_page = $this->_get_sync_license_url( $plugin_id, true, $extra );
8708
				}
8709
			} else {
8710
				/**
8711
				 * @author Vova Feldman (@svovaf)
8712
				 * @since  1.1.9 If site installed with a valid license, sync license.
8713
				 */
8714
				if ( $this->is_paying() ) {
8715
					$this->_sync_plugin_license( true );
8716
				}
8717
8718
				// Reload the page with the keys.
8719
				$next_page = $this->is_anonymous() ?
8720
					// If user previously skipped, redirect to account page.
8721
					$this->get_account_url( false, $extra ) :
8722
					$this->get_after_activation_url( 'after_connect_url' );
8723
			}
8724
8725
			if ( ! empty( $next_page ) && $redirect ) {
8726
				fs_redirect( $next_page );
8727
			}
8728
8729
			return $next_page;
8730
		}
8731
8732
		/**
8733
		 * Install plugin with new user information after approval.
8734
		 *
8735
		 * @author Vova Feldman (@svovaf)
8736
		 * @since  1.0.7
8737
		 */
8738
		function _install_with_new_user() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8739
			$this->_logger->entrance();
8740
8741
			if ( $this->is_registered() ) {
8742
				return;
8743
			}
8744
8745
			if ( ( $this->is_plugin() && fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) ||
8746
				// @todo This logic should be improved because it's executed on every load of a theme.
8747
			     $this->is_theme()
8748
			) {
8749
//				check_admin_referer( $this->_slug . '_activate_new' );
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
8750
8751
				if ( fs_request_has( 'user_secret_key' ) ) {
8752
					$this->install_with_new_user(
8753
						fs_request_get( 'user_id' ),
8754
						fs_request_get( 'user_public_key' ),
8755
						fs_request_get( 'user_secret_key' ),
8756
						fs_request_get( 'install_id' ),
8757
						fs_request_get( 'install_public_key' ),
8758
						fs_request_get( 'install_secret_key' ),
8759
						true,
8760
						fs_request_get_bool( 'auto_install' )
8761
					);
8762
				} else if ( fs_request_has( 'pending_activation' ) ) {
8763
					$this->set_pending_confirmation( fs_request_get( 'user_email' ), true );
8764
				}
8765
			}
8766
		}
8767
8768
		/**
8769
		 * Install plugin with new user.
8770
		 *
8771
		 * @author Vova Feldman (@svovaf)
8772
		 * @since  1.1.7.4
8773
		 *
8774
		 * @param number $user_id
8775
		 * @param string $user_public_key
8776
		 * @param string $user_secret_key
8777
		 * @param number $install_id
8778
		 * @param string $install_public_key
8779
		 * @param string $install_secret_key
8780
		 * @param bool   $redirect
8781
		 * @param bool   $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will
8782
		 *                             redirect (or return a URL) to the account page with a special parameter to
8783
		 *                             trigger the auto installation processes.
8784
		 *
8785
		 * @return string If redirect is `false`, returns the next page the user should be redirected to.
8786
		 */
8787
		private function install_with_new_user(
8788
			$user_id,
8789
			$user_public_key,
8790
			$user_secret_key,
8791
			$install_id,
8792
			$install_public_key,
8793
			$install_secret_key,
8794
			$redirect = true,
8795
			$auto_install = false
8796
		) {
8797
			$user             = new FS_User();
8798
			$user->id         = $user_id;
8799
			$user->public_key = $user_public_key;
8800
			$user->secret_key = $user_secret_key;
8801
8802
			$this->_user = $user;
8803
			$user_result = $this->get_api_user_scope()->get();
8804
			$user        = new FS_User( $user_result );
8805
			$this->_user = $user;
8806
8807
			$site             = new FS_Site();
8808
			$site->id         = $install_id;
8809
			$site->public_key = $install_public_key;
8810
			$site->secret_key = $install_secret_key;
8811
8812
			$this->_site = $site;
8813
			$site_result = $this->get_api_site_scope()->get();
8814
			$site        = new FS_Site( $site_result );
8815
			$this->_site = $site;
8816
8817
			return $this->setup_account(
8818
				$this->_user,
8819
				$this->_site,
8820
				$redirect,
8821
				$auto_install
8822
			);
8823
		}
8824
8825
		/**
8826
		 * @author Vova Feldman (@svovaf)
8827
		 * @since  1.1.7.4
8828
		 *
8829
		 * @param string|bool $email
8830
		 * @param bool        $redirect
8831
		 * @param string|bool $license_key      Since 1.2.1.5
8832
		 * @param bool        $is_pending_trial Since 1.2.1.5
8833
		 *
8834
		 * @return string Since 1.2.1.5 if $redirect is `false`, return the pending activation page.
8835
		 */
8836
		private function set_pending_confirmation(
8837
			$email = false,
8838
			$redirect = true,
8839
			$license_key = false,
8840
			$is_pending_trial = false
8841
		) {
8842
			if ( $this->_ignore_pending_mode ) {
8843
				/**
8844
				 * If explicitly asked to ignore pending mode, set to anonymous mode
8845
				 * if require confirmation before finalizing the opt-in.
8846
				 *
8847
				 * @author Vova Feldman
8848
				 * @since  1.2.1.6
8849
				 */
8850
				$this->skip_connection();
8851
			} else {
8852
				// Install must be activated via email since
8853
				// user with the same email already exist.
8854
				$this->_storage->is_pending_activation = true;
8855
				$this->_add_pending_activation_notice( $email, $is_pending_trial );
8856
			}
8857
8858
			if ( ! empty( $license_key ) ) {
8859
				$this->_storage->pending_license_key = $license_key;
0 ignored issues
show
Documentation introduced by
The property pending_license_key does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
8860
			}
8861
8862
			// Remove the opt-in sticky notice.
8863
			$this->_admin_notices->remove_sticky( array(
8864
				'connect_account',
8865
				'trial_promotion',
8866
			) );
8867
8868
			$next_page = $this->get_after_activation_url( 'after_pending_connect_url' );
8869
8870
			// Reload the page with with pending activation message.
8871
			if ( $redirect ) {
8872
				fs_redirect( $next_page );
8873
			}
8874
8875
			return $next_page;
8876
		}
8877
8878
		/**
8879
		 * Install plugin with current logged WP user info.
8880
		 *
8881
		 * @author Vova Feldman (@svovaf)
8882
		 * @since  1.0.7
8883
		 */
8884
		function _install_with_current_user() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8885
			$this->_logger->entrance();
8886
8887
			if ( $this->is_registered() ) {
8888
				return;
8889
			}
8890
8891
			if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) && fs_request_is_post() ) {
8892
//				check_admin_referer( 'activate_existing_' . $this->_plugin->public_key );
0 ignored issues
show
Unused Code Comprehensibility introduced by
44% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
8893
8894
				/**
8895
				 * @author Vova Feldman (@svovaf)
8896
				 * @since  1.1.9 Add license key if given.
8897
				 */
8898
				$license_key = fs_request_get( 'license_secret_key' );
8899
8900
				$this->install_with_current_user( $license_key );
8901
			}
8902
		}
8903
8904
8905
		/**
8906
		 * @author Vova Feldman (@svovaf)
8907
		 * @since  1.1.7.4
8908
		 *
8909
		 * @param string|bool $license_key
8910
		 * @param number|bool $trial_plan_id
8911
		 * @param bool        $redirect
8912
		 *
8913
		 * @return string|object If redirect is `false`, returns the next page the user should be redirected to, or the
8914
		 *                       API error object if failed to install.
8915
		 */
8916
		private function install_with_current_user(
8917
			$license_key = false,
8918
			$trial_plan_id = false,
8919
			$redirect = true
8920
		) {
8921
			// Get current logged WP user.
8922
			$current_user = self::_get_current_wp_user();
8923
8924
			// Find the relevant FS user by the email.
8925
			$user = self::_get_user_by_email( $current_user->user_email );
8926
8927
			// We have to set the user before getting user scope API handler.
8928
			$this->_user = $user;
0 ignored issues
show
Documentation Bug introduced by
It seems like $user can also be of type boolean. However, the property $_user is declared as type object<FS_User>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
8929
8930
			$extra_install_params = array(
8931
				'uid' => $this->get_anonymous_id(),
8932
			);
8933
8934
			if ( ! empty( $license_key ) ) {
8935
				$filtered_license_key                = $this->apply_filters( 'license_key', $license_key );
8936
				$extra_install_params['license_key'] = $filtered_license_key;
8937
			} else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) {
8938
				$extra_install_params['trial_plan_id'] = $trial_plan_id;
8939
			}
8940
8941
			$args = $this->get_install_data_for_api( $extra_install_params, false, false );
8942
8943
			// Install the plugin.
8944
			$install = $this->get_api_user_scope()->call(
8945
				"/plugins/{$this->get_id()}/installs.json",
8946
				'post',
8947
				$args
8948
			);
8949
8950
			if ( ! $this->is_api_result_entity( $install ) ) {
8951
				if ( ! empty( $args['license_key'] ) ) {
8952
					// Pass full the fully entered license key to the failure handler.
8953
					$args['license_key'] = $license_key;
8954
				}
8955
8956
				$install = $this->apply_filters( 'after_install_failure', $install, $args );
8957
8958
				$this->_admin_notices->add(
8959
					sprintf( $this->get_text( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
8960
					$this->get_text( 'contact-us-with-error-message' ) . ' ' . '<b>' . $install->error->message . '</b>',
8961
					$this->get_text( 'oops' ) . '...',
8962
					'error'
8963
				);
8964
8965
				if ( $redirect ) {
8966
                    /**
8967
                     * We set the user before getting the user scope API handler, so the user became temporarily
8968
                     * registered (`is_registered() = true`). Since the API returned an error and we will redirect,
8969
                     * we have to set the user to `null`, otherwise, the user will be redirected to the wrong
8970
                     * activation page based on the return value of `is_registered()`. In addition, in case the
8971
                     * context plugin doesn't have a settings menu and the default page is the `Plugins` page,
8972
                     * misleading plugin activation errors will be shown on the `Plugins` page.
8973
                     *
8974
                     * @author Leo Fajardo (@leorw)
8975
                     */
8976
                    $this->_user = null;
8977
8978
                    fs_redirect( $this->get_activation_url( array( 'error' => $install->error->message ) ) );
8979
				}
8980
8981
				return $install;
8982
			}
8983
8984
			$site        = new FS_Site( $install );
8985
			$this->_site = $site;
8986
8987
			return $this->setup_account( $this->_user, $this->_site, $redirect );
0 ignored issues
show
Bug introduced by
It seems like $this->_user can also be of type boolean; however, Freemius::setup_account() does only seem to accept object<FS_User>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
8988
		}
8989
8990
		/**
8991
		 * Tries to activate add-on account based on parent plugin info.
8992
		 *
8993
		 * @author Vova Feldman (@svovaf)
8994
		 * @since  1.0.6
8995
		 *
8996
		 * @param Freemius $parent_fs
8997
		 */
8998
		private function _activate_addon_account( Freemius $parent_fs ) {
8999
			if ( $this->is_registered() ) {
9000
				// Already activated.
9001
				return;
9002
			}
9003
9004
			// Activate add-on with parent plugin credentials.
9005
			$addon_install = $parent_fs->get_api_site_scope()->call(
9006
				"/addons/{$this->_plugin->id}/installs.json",
9007
				'post',
9008
				$this->get_install_data_for_api( array(
9009
					'uid' => $this->get_anonymous_id(),
9010
				), false, false )
9011
			);
9012
9013
			if ( isset( $addon_install->error ) ) {
9014
				$this->_admin_notices->add(
9015
					sprintf( $this->get_text( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
9016
					$this->get_text( 'contact-us-with-error-message' ) . ' ' . '<b>' . $addon_install->error->message . '</b>',
9017
					$this->get_text( 'oops' ) . '...',
9018
					'error'
9019
				);
9020
9021
				return;
9022
			}
9023
9024
			// First of all, set site info - otherwise we won't
9025
			// be able to invoke API calls.
9026
			$this->_site = new FS_Site( $addon_install );
9027
9028
			// Sync add-on plans.
9029
			$this->_sync_plans();
9030
9031
			// Get site's current plan.
9032
			$this->_site->plan = $this->_get_plan_by_id( $this->_site->plan->id );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->_get_plan_by_id($this->_site->plan->id) can also be of type false. However, the property $plan is declared as type object<FS_Plugin_Plan>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
9033
9034
			// Get user information based on parent's plugin.
9035
			$user = $parent_fs->get_user();
9036
9037
			$this->_set_account( $user, $this->_site );
9038
9039
			// Sync licenses.
9040
			$this->_sync_licenses();
9041
9042
			// Try to activate premium license.
9043
			$this->_activate_license( true );
9044
		}
9045
9046
		/**
9047
		 * Tries to activate parent account based on add-on's info.
9048
		 *
9049
		 * @author Vova Feldman (@svovaf)
9050
		 * @since  1.2.2.7
9051
		 *
9052
		 * @param Freemius $parent_fs
9053
		 */
9054
		private function activate_parent_account( Freemius $parent_fs ) {
9055
			if ( ! $this->is_addon() ) {
9056
				// This is not an add-on.
9057
				return;
9058
			}
9059
9060
			if ( $parent_fs->is_registered() ) {
9061
				// Already activated.
9062
				return;
9063
			}
9064
9065
			// Activate parent with add-on's user credentials.
9066
			$parent_install = $this->get_api_user_scope()->call(
9067
				"/plugins/{$parent_fs->_plugin->id}/installs.json",
9068
				'post',
9069
				$parent_fs->get_install_data_for_api( array(
9070
					'uid' => $parent_fs->get_anonymous_id(),
9071
				), false, false )
9072
			);
9073
9074
			if ( isset( $parent_install->error ) ) {
9075
				$this->_admin_notices->add(
9076
					sprintf( $this->get_text( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
9077
					$this->get_text( 'contact-us-with-error-message' ) . ' ' . '<b>' . $parent_install->error->message . '</b>',
9078
					$this->get_text( 'oops' ) . '...',
9079
					'error'
9080
				);
9081
9082
				return;
9083
			}
9084
9085
            $parent_fs->_admin_notices->remove_sticky( 'connect_account' );
9086
9087
            if ( $parent_fs->is_pending_activation() ) {
9088
                $parent_fs->_admin_notices->remove_sticky( 'activation_pending' );
9089
9090
                unset( $parent_fs->_storage->is_pending_activation );
9091
            }
9092
9093
			// First of all, set site info - otherwise we won't
9094
			// be able to invoke API calls.
9095
			$parent_fs->_site = new FS_Site( $parent_install );
9096
9097
			// Sync add-on plans.
9098
			$parent_fs->_sync_plans();
9099
9100
			// Get site's current plan.
9101
			$parent_fs->_site->plan = $parent_fs->_get_plan_by_id( $parent_fs->_site->plan->id );
0 ignored issues
show
Documentation Bug introduced by
It seems like $parent_fs->_get_plan_by...nt_fs->_site->plan->id) can also be of type false. However, the property $plan is declared as type object<FS_Plugin_Plan>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
9102
9103
			// Get user information based on parent's plugin.
9104
			$user = $this->get_user();
9105
9106
			$parent_fs->_set_account( $user, $parent_fs->_site );
9107
		}
9108
9109
		#endregion
9110
9111
		#----------------------------------------------------------------------------------
9112
		#region Admin Menu Items
9113
		#----------------------------------------------------------------------------------
9114
9115
		private $_menu_items = array();
9116
9117
		/**
9118
		 * @author Vova Feldman (@svovaf)
9119
		 * @since  1.2.1.8
9120
		 *
9121
		 * @return array
9122
		 */
9123
		function get_menu_items() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9124
			return $this->_menu_items;
9125
		}
9126
9127
		/**
9128
		 * @author Vova Feldman (@svovaf)
9129
		 * @since  1.0.7
9130
		 *
9131
		 * @return string
9132
		 */
9133
		function get_menu_slug() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9134
			return $this->_menu->get_slug();
9135
		}
9136
9137
		/**
9138
		 * @author Vova Feldman (@svovaf)
9139
		 * @since  1.0.9
9140
		 */
9141
		function _prepare_admin_menu() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9142
//			if ( ! $this->is_on() ) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
9143
//				return;
9144
//			}
9145
9146
			if ( ! $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) {
9147
				$this->_menu->remove_menu_item();
9148
			} else {
9149
				$this->do_action( 'before_admin_menu_init' );
9150
9151
				$this->add_menu_action();
9152
				$this->add_submenu_items();
9153
			}
9154
		}
9155
9156
		/**
9157
		 * Admin dashboard menu items modifications.
9158
		 *
9159
		 * NOTE: admin_menu action executed before admin_init.
9160
		 *
9161
		 * @author Vova Feldman (@svovaf)
9162
		 * @since  1.0.7
9163
		 *
9164
		 */
9165
		private function add_menu_action() {
9166
			if ( $this->is_activation_mode() ) {
9167
				if ( $this->is_plugin() || ( $this->has_settings_menu() && ! $this->is_free_wp_org_theme() ) ) {
9168
					$this->override_plugin_menu_with_activation();
9169
				} else {
9170
					/**
9171
					 * Handle theme opt-in when the opt-in form shows as a dialog box in the themes page.
9172
					 */
9173
					if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) {
9174
						add_action( 'load-themes.php', array( &$this, '_install_with_current_user' ) );
9175
					} else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ||
9176
					            fs_request_get_bool( 'pending_activation' )
9177
					) {
9178
						add_action( 'load-themes.php', array( &$this, '_install_with_new_user' ) );
9179
					}
9180
				}
9181
			} else {
9182
				if ( ! $this->is_registered() ) {
9183
					// If not registered try to install user.
9184
					if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) {
9185
						$this->_install_with_new_user();
9186
					}
9187
				} else if (
9188
					fs_request_is_action( 'sync_user' ) &&
9189
					( ! $this->has_settings_menu() || $this->is_free_wp_org_theme() )
9190
				) {
9191
					$this->_handle_account_user_sync();
9192
				}
9193
			}
9194
		}
9195
9196
		/**
9197
		 * @author Vova Feldman (@svovaf)
9198
		 * @since  1.0.1
9199
		 */
9200
		function _redirect_on_clicked_menu_link() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
Coding Style introduced by
_redirect_on_clicked_menu_link uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
9201
			$this->_logger->entrance();
9202
9203
			$page = strtolower( isset( $_REQUEST['page'] ) ? $_REQUEST['page'] : '' );
9204
9205
			$this->_logger->log( 'page = ' . $page );
9206
9207
			foreach ( $this->_menu_items as $priority => $items ) {
9208
				foreach ( $items as $item ) {
9209
					if ( isset( $item['url'] ) ) {
9210
						if ( $page === $this->_menu->get_slug( strtolower( $item['menu_slug'] ) ) ) {
9211
							$this->_logger->log( 'Redirecting to ' . $item['url'] );
9212
9213
							fs_redirect( $item['url'] );
9214
						}
9215
					}
9216
				}
9217
			}
9218
		}
9219
9220
		/**
9221
		 * Remove plugin's all admin menu items & pages, and replace with activation page.
9222
		 *
9223
		 * @author Vova Feldman (@svovaf)
9224
		 * @since  1.0.1
9225
		 */
9226
		private function override_plugin_menu_with_activation() {
9227
			$this->_logger->entrance();
9228
9229
			$hook = false;
9230
9231
			if ( ! $this->_menu->has_menu() ) {
9232
				// Add the opt-in page without a menu item.
9233
				$hook = FS_Admin_Menu_Manager::add_subpage(
9234
					null,
9235
					$this->get_plugin_name(),
9236
					$this->get_plugin_name(),
9237
					'manage_options',
9238
					$this->_slug,
9239
					array( &$this, '_connect_page_render' )
9240
				);
9241
			} else if ( $this->_menu->is_top_level() ) {
9242
				$hook = $this->_menu->override_menu_item( array( &$this, '_connect_page_render' ) );
9243
9244
				if ( false === $hook ) {
9245
					// Create new menu item just for the opt-in.
9246
					$hook = FS_Admin_Menu_Manager::add_page(
9247
						$this->get_plugin_name(),
9248
						$this->get_plugin_name(),
9249
						'manage_options',
9250
						$this->_menu->get_slug(),
9251
						array( &$this, '_connect_page_render' )
9252
					);
9253
				}
9254
			} else {
9255
				$menus = array( $this->_menu->get_parent_slug() );
9256
9257
				if ( $this->_menu->is_override_exact() ) {
9258
					// Make sure the current page is matching the activation page.
9259
					if ( ! $this->is_matching_url( $this->get_activation_url() ) ) {
9260
						return;
9261
					}
9262
				}
9263
9264
				foreach ( $menus as $parent_slug ) {
9265
					$hook = $this->_menu->override_submenu_action(
9266
						$parent_slug,
9267
						$this->_menu->get_raw_slug(),
9268
						array( &$this, '_connect_page_render' )
9269
					);
9270
9271
					if ( false !== $hook ) {
9272
						// Found plugin's submenu item.
9273
						break;
9274
					}
9275
				}
9276
			}
9277
9278
			if ( $this->is_activation_page() ) {
9279
				// Clean admin page from distracting content.
9280
				self::_clean_admin_content_section();
9281
			}
9282
9283
			if ( false !== $hook ) {
9284
				if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) {
9285
                    $this->_install_with_current_user();
9286
				} else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) {
9287
                    $this->_install_with_new_user();
9288
				}
9289
			}
9290
		}
9291
9292
		/**
9293
		 * @author Leo Fajardo (leorw)
9294
		 * @since  1.2.1
9295
		 *
9296
		 * return string
9297
		 */
9298
		function get_top_level_menu_capability() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9299
			global $menu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
9300
9301
			$top_level_menu_slug = $this->get_top_level_menu_slug();
9302
9303
			foreach ( $menu as $menu_info ) {
9304
				/**
9305
				 * The second element in the menu info array is the capability/role that has access to the menu and the
9306
				 * third element is the menu slug.
9307
				 */
9308
				if ( $menu_info[2] === $top_level_menu_slug ) {
9309
					return $menu_info[1];
9310
				}
9311
			}
9312
9313
			return 'read';
9314
		}
9315
9316
		/**
9317
		 * @author Vova Feldman (@svovaf)
9318
		 * @since  1.0.0
9319
		 *
9320
		 * @return string
9321
		 */
9322
		private function get_top_level_menu_slug() {
9323
			return ( $this->is_addon() ?
9324
				$this->get_parent_instance()->_menu->get_top_level_menu_slug() :
9325
				$this->_menu->get_top_level_menu_slug() );
9326
		}
9327
9328
		/**
9329
		 * @author Vova Feldman (@svovaf)
9330
		 * @since  1.2.2.7
9331
		 *
9332
		 * @return string
9333
		 */
9334
		function get_pricing_cta_label() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9335
			$label = 'upgrade';
9336
9337
			if ( $this->is_in_trial_promotion() &&
9338
			     ! $this->is_paying_or_trial()
9339
			) {
9340
				// If running a trial promotion, modify the pricing to load the trial.
9341
				$label = 'start-trial';
9342
			} else if ( $this->is_paying() ) {
9343
				$label = 'pricing';
9344
			}
9345
9346
			return $label;
9347
		}
9348
9349
		/**
9350
		 * @author Vova Feldman (@svovaf)
9351
		 * @since  1.2.2.7
9352
		 *
9353
		 * @return bool
9354
		 */
9355
		function is_pricing_page_visible() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9356
			return (
9357
				// Has at least one paid plan.
9358
				$this->has_paid_plan() &&
9359
				// Didn't ask to hide the pricing page.
9360
				$this->is_page_visible( 'pricing' ) &&
9361
				// Don't have a valid active license or has more than one plan.
9362
				( ! $this->is_paying() || ! $this->is_single_plan() )
9363
			);
9364
		}
9365
9366
		/**
9367
		 * Add default Freemius menu items.
9368
		 *
9369
		 * @author Vova Feldman (@svovaf)
9370
		 * @since  1.0.0
9371
		 */
9372
		private function add_submenu_items() {
9373
			$this->_logger->entrance();
9374
9375
			if ( ! $this->is_addon() ) {
9376
				/**
9377
				 * @since 1.2.2.7 Also add submenu items when running in a free .org theme so the tabs will be visible.
9378
				 */
9379
				if ( ! $this->is_activation_mode() || $this->is_free_wp_org_theme() ) {
9380
                    if ( $this->has_affiliate_program() ) {
9381
                        // Add affiliation page.
9382
                        $this->add_submenu_item(
9383
                            $this->get_text( 'affiliation' ),
9384
                            array( &$this, '_affiliation_page_render' ),
9385
                            $this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'affiliation' ),
9386
                            'manage_options',
9387
                            'affiliation',
9388
                            'Freemius::_clean_admin_content_section',
9389
                            WP_FS__DEFAULT_PRIORITY,
9390
                            $this->is_submenu_item_visible( 'affiliation' )
9391
                        );
9392
                    }
9393
9394
					if ( $this->is_registered() ) {
9395
						$show_account = (
9396
							$this->is_submenu_item_visible( 'account' ) &&
9397
							/**
9398
							 * @since 1.2.2.7 Don't show the Account for free WP.org themes without any paid plans.
9399
							 */
9400
							( ! $this->is_free_wp_org_theme() || $this->has_paid_plan() )
9401
						);
9402
9403
						// Add user account page.
9404
						$this->add_submenu_item(
9405
							$this->get_text( 'account' ),
9406
							array( &$this, '_account_page_render' ),
9407
							$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'account' ),
9408
							'manage_options',
9409
							'account',
9410
							array( &$this, '_account_page_load' ),
9411
							WP_FS__DEFAULT_PRIORITY,
9412
							$show_account
9413
						);
9414
					}
9415
9416
					// Add contact page.
9417
					$this->add_submenu_item(
9418
						$this->get_text( 'contact-us' ),
9419
						array( &$this, '_contact_page_render' ),
9420
						$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'contact-us' ),
9421
						'manage_options',
9422
						'contact',
9423
						'Freemius::_clean_admin_content_section',
9424
						WP_FS__DEFAULT_PRIORITY,
9425
						$this->is_submenu_item_visible( 'contact' )
9426
					);
9427
9428
					if ( $this->has_addons() ) {
9429
						$this->add_submenu_item(
9430
							$this->get_text( 'add-ons' ),
9431
							array( &$this, '_addons_page_render' ),
9432
							$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'add-ons' ),
9433
							'manage_options',
9434
							'addons',
9435
							array( &$this, '_addons_page_load' ),
9436
							WP_FS__LOWEST_PRIORITY - 1,
9437
							$this->is_submenu_item_visible( 'addons' )
9438
						);
9439
					}
9440
9441
					$show_pricing = (
9442
						$this->is_submenu_item_visible( 'pricing' ) &&
9443
						$this->is_pricing_page_visible()
9444
					);
9445
9446
					$pricing_cta_slug = $this->get_pricing_cta_label();
9447
					$pricing_class    = 'upgrade-mode';
9448
					if ( $show_pricing ) {
9449
						if ( $this->is_in_trial_promotion() &&
9450
						     ! $this->is_paying_or_trial()
9451
						) {
9452
							// If running a trial promotion, modify the pricing to load the trial.
9453
							$pricing_class    = 'trial-mode';
9454
						} else if ( $this->is_paying() ) {
9455
							$pricing_class    = '';
9456
						}
9457
					}
9458
9459
					// Add upgrade/pricing page.
9460
					$this->add_submenu_item(
9461
						$this->get_text( $pricing_cta_slug ) . '&nbsp;&nbsp;' . ( is_rtl() ? '&#x2190;' : '&#x27a4;' ),
9462
						array( &$this, '_pricing_page_render' ),
9463
						$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'pricing' ),
9464
						'manage_options',
9465
						'pricing',
9466
						'Freemius::_clean_admin_content_section',
9467
						WP_FS__LOWEST_PRIORITY,
9468
						$show_pricing,
9469
						$pricing_class
9470
					);
9471
				}
9472
			}
9473
9474
9475
			if ( 0 < count( $this->_menu_items ) ) {
9476
				if ( ! $this->_menu->is_top_level() ) {
9477
					fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
9478
9479
					// Append submenu items right after the plugin's submenu item.
9480
					$this->order_sub_submenu_items();
9481
				} else {
9482
					// Append submenu items.
9483
					$this->embed_submenu_items();
9484
				}
9485
			}
9486
		}
9487
9488
		/**
9489
		 * Moved the actual submenu item additions to a separated function,
9490
		 * in order to support sub-submenu items when the plugin's settings
9491
		 * only have a submenu and not top-level menu item.
9492
		 *
9493
		 * @author Vova Feldman (@svovaf)
9494
		 * @since  1.1.4
9495
		 */
9496
		private function embed_submenu_items() {
9497
			$item_template = $this->_menu->is_top_level() ?
9498
				'<span class="fs-submenu-item %s %s %s">%s</span>' :
9499
				'<span class="fs-submenu-item fs-sub %s %s %s">%s</span>';
9500
9501
			$top_level_menu_capability = $this->get_top_level_menu_capability();
9502
9503
			ksort( $this->_menu_items );
9504
9505
			foreach ( $this->_menu_items as $priority => $items ) {
9506
				foreach ( $items as $item ) {
9507
					$capability = ( ! empty( $item['capability'] ) ? $item['capability'] : $top_level_menu_capability );
9508
9509
					$menu_item = sprintf(
9510
						$item_template,
9511
						$this->get_unique_affix(),
9512
						$item['menu_slug'],
9513
						! empty( $item['class'] ) ? $item['class'] : '',
9514
						$item['menu_title']
9515
					);
9516
9517
					$menu_slug = $this->_menu->get_slug( $item['menu_slug'] );
9518
9519
					if ( ! isset( $item['url'] ) ) {
9520
						$hook = FS_Admin_Menu_Manager::add_subpage(
9521
							$item['show_submenu'] ?
9522
								$this->get_top_level_menu_slug() :
9523
								null,
9524
							$item['page_title'],
9525
							$menu_item,
9526
							$capability,
9527
							$menu_slug,
9528
							$item['render_function']
9529
						);
9530
9531
						if ( false !== $item['before_render_function'] ) {
9532
							add_action( "load-$hook", $item['before_render_function'] );
9533
						}
9534
					} else {
9535
						FS_Admin_Menu_Manager::add_subpage(
9536
							$item['show_submenu'] ?
9537
								$this->get_top_level_menu_slug() :
9538
								null,
9539
							$item['page_title'],
9540
							$menu_item,
9541
							$capability,
9542
							$menu_slug,
9543
							array( $this, '' )
9544
						);
9545
					}
9546
				}
9547
			}
9548
		}
9549
9550
		/**
9551
		 * Re-order the submenu items so all Freemius added new submenu items
9552
		 * are added right after the plugin's settings submenu item.
9553
		 *
9554
		 * @author Vova Feldman (@svovaf)
9555
		 * @since  1.1.4
9556
		 */
9557
		private function order_sub_submenu_items() {
9558
			global $submenu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
9559
9560
			$menu_slug = $this->_menu->get_top_level_menu_slug();
9561
9562
			/**
9563
			 * Before "admin_menu" fires, WordPress will loop over the default submenus and remove pages for which the user
9564
			 * does not have permissions. So in case a plugin does not have top-level menu but does have submenus under any
9565
			 * of the default menus, only users that have the right role can access its sub-submenus (Account, Contact Us,
9566
			 * Support Forum, etc.) since $submenu[ $menu_slug ] will be empty if the user doesn't have permission.
9567
			 *
9568
			 * In case a plugin does not have submenus under any of the default menus but does have submenus under the menu
9569
			 * of another plugin, only users that have the right role can access its sub-submenus since we will use the
9570
			 * capability needed to access the parent menu as the capability for the submenus that we will add.
9571
			 */
9572
			if ( empty( $submenu[ $menu_slug ] ) ) {
9573
				return;
9574
			}
9575
9576
			$top_level_menu = &$submenu[ $menu_slug ];
9577
9578
			$all_submenu_items_after = array();
9579
9580
			$found_submenu_item = false;
9581
9582
			foreach ( $top_level_menu as $submenu_id => $meta ) {
9583
				if ( $found_submenu_item ) {
9584
					// Remove all submenu items after the plugin's submenu item.
9585
					$all_submenu_items_after[] = $meta;
9586
					unset( $top_level_menu[ $submenu_id ] );
9587
				}
9588
9589
				if ( $this->_menu->get_raw_slug() === $meta[2] ) {
9590
					// Found the submenu item, put all below.
9591
					$found_submenu_item = true;
9592
					continue;
9593
				}
9594
			}
9595
9596
			// Embed all plugin's new submenu items.
9597
			$this->embed_submenu_items();
9598
9599
			// Start with specially high number to make sure it's appended.
9600
			$i = max( 10000, max( array_keys( $top_level_menu ) ) + 1 );
9601
			foreach ( $all_submenu_items_after as $meta ) {
9602
				$top_level_menu[ $i ] = $meta;
9603
				$i ++;
9604
			}
9605
9606
			// Sort submenu items.
9607
			ksort( $top_level_menu );
9608
		}
9609
9610
		/**
9611
		 * Helper method to return the module's support forum URL.
9612
		 *
9613
		 * @author Vova Feldman (@svovaf)
9614
		 * @since  1.2.2.7
9615
		 *
9616
		 * @return string
9617
		 */
9618
		function get_support_forum_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9619
			return $this->apply_filters( 'support_forum_url', "https://wordpress.org/support/{$this->_module_type}/{$this->_slug}" );
9620
		}
9621
9622
		/**
9623
		 * Displays the Support Forum link when enabled.
9624
		 *
9625
		 * Can be filtered like so:
9626
		 *
9627
		 *  function _fs_show_support_menu( $is_visible, $menu_id ) {
9628
		 *      if ( 'support' === $menu_id ) {
9629
		 *            return _fs->is_registered();
9630
		 *        }
9631
		 *        return $is_visible;
9632
		 *    }
9633
		 *    _fs()->add_filter('is_submenu_visible', '_fs_show_support_menu', 10, 2);
9634
		 *
9635
		 */
9636
		function _add_default_submenu_items() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9637
			if ( ! $this->is_on() ) {
9638
				return;
9639
			}
9640
9641
			if ( ! $this->is_activation_mode() ) {
9642
				$this->add_submenu_link_item(
9643
					$this->apply_filters( 'support_forum_submenu', $this->get_text( 'support-forum' ) ),
9644
					$this->get_support_forum_url(),
9645
					'wp-support-forum',
0 ignored issues
show
Documentation introduced by
'wp-support-forum' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
9646
					null,
9647
					50,
9648
					$this->is_submenu_item_visible( 'support' )
9649
				);
9650
			}
9651
		}
9652
9653
		/**
9654
		 * @author Vova Feldman (@svovaf)
9655
		 * @since  1.0.1
9656
		 *
9657
		 * @param string        $menu_title
9658
		 * @param callable      $render_function
9659
		 * @param bool|string   $page_title
9660
		 * @param string        $capability
9661
		 * @param bool|string   $menu_slug
9662
		 * @param bool|callable $before_render_function
9663
		 * @param int           $priority
9664
		 * @param bool          $show_submenu
9665
		 * @param string        $class Since 1.2.1.5 can add custom classes to menu items.
9666
		 */
9667
		function add_submenu_item(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9668
			$menu_title,
9669
			$render_function,
9670
			$page_title = false,
9671
			$capability = 'manage_options',
9672
			$menu_slug = false,
9673
			$before_render_function = false,
9674
			$priority = WP_FS__DEFAULT_PRIORITY,
9675
			$show_submenu = true,
9676
			$class = ''
9677
		) {
9678
			$this->_logger->entrance( 'Title = ' . $menu_title );
9679
9680
			if ( $this->is_addon() ) {
9681
				$parent_fs = $this->get_parent_instance();
9682
9683
				if ( is_object( $parent_fs ) ) {
9684
					$parent_fs->add_submenu_item(
9685
						$menu_title,
9686
						$render_function,
9687
						$page_title,
9688
						$capability,
9689
						$menu_slug,
9690
						$before_render_function,
9691
						$priority,
9692
						$show_submenu,
9693
						$class
9694
					);
9695
9696
					return;
9697
				}
9698
			}
9699
9700
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
9701
				$this->_menu_items[ $priority ] = array();
9702
			}
9703
9704
			$this->_menu_items[ $priority ][] = array(
9705
				'page_title'             => is_string( $page_title ) ? $page_title : $menu_title,
9706
				'menu_title'             => $menu_title,
9707
				'capability'             => $capability,
9708
				'menu_slug'              => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ),
9709
				'render_function'        => $render_function,
9710
				'before_render_function' => $before_render_function,
9711
				'show_submenu'           => $show_submenu,
9712
				'class'                  => $class,
9713
			);
9714
		}
9715
9716
		/**
9717
		 * @author Vova Feldman (@svovaf)
9718
		 * @since  1.0.1
9719
		 *
9720
		 * @param string $menu_title
9721
		 * @param string $url
9722
		 * @param bool   $menu_slug
9723
		 * @param string $capability
9724
		 * @param int    $priority
9725
		 * @param bool   $show_submenu
9726
		 */
9727
		function add_submenu_link_item(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9728
			$menu_title,
9729
			$url,
9730
			$menu_slug = false,
9731
			$capability = 'read',
9732
			$priority = WP_FS__DEFAULT_PRIORITY,
9733
			$show_submenu = true
9734
		) {
9735
			$this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url );
9736
9737
			if ( $this->is_addon() ) {
9738
				$parent_fs = $this->get_parent_instance();
9739
9740
				if ( is_object( $parent_fs ) ) {
9741
					$parent_fs->add_submenu_link_item(
9742
						$menu_title,
9743
						$url,
9744
						$menu_slug,
9745
						$capability,
9746
						$priority,
9747
						$show_submenu
9748
					);
9749
9750
					return;
9751
				}
9752
			}
9753
9754
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
9755
				$this->_menu_items[ $priority ] = array();
9756
			}
9757
9758
			$this->_menu_items[ $priority ][] = array(
9759
				'menu_title'             => $menu_title,
9760
				'capability'             => $capability,
9761
				'menu_slug'              => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ),
9762
				'url'                    => $url,
9763
				'page_title'             => $menu_title,
9764
				'render_function'        => 'fs_dummy',
9765
				'before_render_function' => '',
9766
				'show_submenu'           => $show_submenu,
9767
			);
9768
		}
9769
9770
		#endregion ------------------------------------------------------------------
9771
9772
9773
		#--------------------------------------------------------------------------------
9774
		#region Actions / Hooks / Filters
9775
		#--------------------------------------------------------------------------------
9776
9777
		/**
9778
		 * @author Vova Feldman (@svovaf)
9779
		 * @since  1.1.7
9780
		 *
9781
		 * @param string $tag
9782
		 *
9783
		 * @return string
9784
		 */
9785
		public function get_action_tag( $tag ) {
9786
			return self::get_action_tag_static( $tag, $this->_slug, $this->is_plugin() );
9787
		}
9788
9789
		/**
9790
		 * @author Vova Feldman (@svovaf)
9791
		 * @since  1.2.1.6
9792
		 *
9793
		 * @param string $tag
9794
		 * @param string $slug
9795
		 * @param bool   $is_plugin
9796
		 *
9797
		 * @return string
9798
		 */
9799
		static function get_action_tag_static( $tag, $slug = '', $is_plugin = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9800
			$action = "fs_{$tag}";
9801
9802
			if ( ! empty( $slug ) ) {
9803
				$action .= '_' . self::get_module_unique_affix( $slug, $is_plugin );
9804
			}
9805
9806
			return $action;
9807
		}
9808
9809
		/**
9810
		 * Returns a string that can be used to generate a unique action name,
9811
		 * option name, HTML element ID, or HTML element class.
9812
		 *
9813
		 * @author Leo Fajardo (@leorw)
9814
		 * @since  1.2.2
9815
		 *
9816
		 * @return string
9817
		 */
9818
		public function get_unique_affix() {
9819
			return self::get_module_unique_affix( $this->_slug, $this->is_plugin() );
9820
		}
9821
9822
		/**
9823
		 * Returns a string that can be used to generate a unique action name,
9824
		 * option name, HTML element ID, or HTML element class.
9825
		 *
9826
		 * @author Vova Feldman (@svovaf)
9827
		 * @since  1.2.2.5
9828
		 *
9829
		 * @param string $slug
9830
		 * @param bool   $is_plugin
9831
		 *
9832
		 * @return string
9833
		 */
9834
		static function get_module_unique_affix( $slug, $is_plugin = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9835
			$affix = $slug;
9836
9837
			if ( ! $is_plugin ) {
9838
				$affix .= '-' . WP_FS__MODULE_TYPE_THEME;
9839
			}
9840
9841
			return $affix;
9842
		}
9843
9844
		/**
9845
		 * @author Vova Feldman (@svovaf)
9846
		 * @since  1.2.1
9847
		 * @since  1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are
9848
		 *         based on the slug for backward compatibility.
9849
		 *
9850
		 * @param string $tag
9851
		 *
9852
		 * @return string
9853
		 */
9854
		function get_ajax_action( $tag ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9855
			return self::get_ajax_action_static( $tag, $this->_module_id );
9856
		}
9857
9858
		/**
9859
		 * @author Vova Feldman (@svovaf)
9860
		 * @since  1.2.1.7
9861
		 *
9862
		 * @param string $tag
9863
		 *
9864
		 * @return string
9865
		 */
9866
		function get_ajax_security( $tag ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9867
			return wp_create_nonce( $this->get_ajax_action( $tag ) );
9868
		}
9869
9870
		/**
9871
		 * @author Vova Feldman (@svovaf)
9872
		 * @since  1.2.1.7
9873
		 *
9874
		 * @param string $tag
9875
		 */
9876
		function check_ajax_referer( $tag ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9877
			check_ajax_referer( $this->get_ajax_action( $tag ), 'security' );
9878
		}
9879
9880
		/**
9881
		 * @author Vova Feldman (@svovaf)
9882
		 * @since  1.2.1.6
9883
		 * @since  1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are
9884
		 *         based on the slug for backward compatibility.
9885
		 *
9886
		 * @param string      $tag
9887
		 * @param number|null $module_id
9888
		 *
9889
		 * @return string
9890
		 */
9891
		private static function get_ajax_action_static( $tag, $module_id = null ) {
9892
			$action = "fs_{$tag}";
9893
9894
			if ( ! empty( $module_id ) ) {
9895
				$action .= "_{$module_id}";
9896
			}
9897
9898
			return $action;
9899
		}
9900
9901
		/**
9902
		 * Do action, specific for the current context plugin.
9903
		 *
9904
		 * @author Vova Feldman (@svovaf)
9905
		 * @since  1.0.1
9906
		 *
9907
		 * @param string $tag     The name of the action to be executed.
9908
		 * @param mixed  $arg,... Optional. Additional arguments which are passed on to the
0 ignored issues
show
Bug introduced by
There is no parameter named $arg,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
9909
		 *                        functions hooked to the action. Default empty.
9910
		 *
9911
		 * @uses   do_action()
9912
		 */
9913
		function do_action( $tag, $arg = '' ) {
0 ignored issues
show
Unused Code introduced by
The parameter $arg is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9914
			$this->_logger->entrance( $tag );
9915
9916
			$args = func_get_args();
9917
9918
			call_user_func_array( 'do_action', array_merge(
9919
					array( $this->get_action_tag( $tag ) ),
9920
					array_slice( $args, 1 ) )
9921
			);
9922
		}
9923
9924
		/**
9925
		 * Add action, specific for the current context plugin.
9926
		 *
9927
		 * @author Vova Feldman (@svovaf)
9928
		 * @since  1.0.1
9929
		 *
9930
		 * @param string   $tag
9931
		 * @param callable $function_to_add
9932
		 * @param int      $priority
9933
		 * @param int      $accepted_args
9934
		 *
9935
		 * @uses   add_action()
9936
		 */
9937
		function add_action(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9938
			$tag,
9939
			$function_to_add,
9940
			$priority = WP_FS__DEFAULT_PRIORITY,
9941
			$accepted_args = 1
9942
		) {
9943
			$this->_logger->entrance( $tag );
9944
9945
			add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
9946
		}
9947
9948
		/**
9949
		 * Add AJAX action, specific for the current context plugin.
9950
		 *
9951
		 * @author Vova Feldman (@svovaf)
9952
		 * @since  1.2.1
9953
		 *
9954
		 * @param string   $tag
9955
		 * @param callable $function_to_add
9956
		 * @param int      $priority
9957
		 *
9958
		 * @uses   add_action()
9959
		 *
9960
		 * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
9961
		 */
9962
		function add_ajax_action(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9963
			$tag,
9964
			$function_to_add,
9965
			$priority = WP_FS__DEFAULT_PRIORITY
9966
		) {
9967
			$this->_logger->entrance( $tag );
9968
9969
			return self::add_ajax_action_static(
9970
				$tag,
9971
				$function_to_add,
9972
				$priority,
9973
				$this->_module_id
9974
			);
9975
		}
9976
9977
		/**
9978
		 * Add AJAX action.
9979
		 *
9980
		 * @author Vova Feldman (@svovaf)
9981
		 * @since  1.2.1.6
9982
		 *
9983
		 * @param string      $tag
9984
		 * @param callable    $function_to_add
9985
		 * @param int         $priority
9986
		 * @param number|null $module_id
9987
		 *
9988
		 * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
9989
		 * @uses   add_action()
9990
		 *
9991
		 */
9992
		static function add_ajax_action_static(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9993
			$tag,
9994
			$function_to_add,
9995
			$priority = WP_FS__DEFAULT_PRIORITY,
9996
			$module_id = null
9997
		) {
9998
			self::$_static_logger->entrance( $tag );
9999
10000
			if ( ! self::is_ajax_action_static( $tag, $module_id ) ) {
10001
				return false;
10002
			}
10003
10004
			add_action(
10005
				'wp_ajax_' . self::get_ajax_action_static( $tag, $module_id ),
10006
				$function_to_add,
10007
				$priority,
10008
				0
10009
			);
10010
10011
			self::$_static_logger->info( "$tag AJAX callback action added." );
10012
10013
			return true;
10014
		}
10015
10016
		/**
10017
		 * Send a JSON response back to an Ajax request.
10018
		 *
10019
		 * @author Vova Feldman (@svovaf)
10020
		 * @since  1.2.1.5
10021
		 *
10022
		 * @param mixed $response
10023
		 */
10024
		static function shoot_ajax_response( $response ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10025
			wp_send_json( $response );
10026
		}
10027
10028
		/**
10029
		 * Send a JSON response back to an Ajax request, indicating success.
10030
		 *
10031
		 * @author Vova Feldman (@svovaf)
10032
		 * @since  1.2.1.5
10033
		 *
10034
		 * @param mixed $data Data to encode as JSON, then print and exit.
10035
		 */
10036
		static function shoot_ajax_success( $data = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10037
			wp_send_json_success( $data );
10038
		}
10039
10040
		/**
10041
		 * Send a JSON response back to an Ajax request, indicating failure.
10042
		 *
10043
		 * @author Vova Feldman (@svovaf)
10044
		 * @since  1.2.1.5
10045
		 *
10046
		 * @param mixed $error Optional error message.
10047
		 */
10048
		static function shoot_ajax_failure( $error = '' ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10049
			$result = array( 'success' => false );
10050
			if ( ! empty( $error ) ) {
10051
				$result['error'] = $error;
10052
			}
10053
10054
			wp_send_json( $result );
10055
		}
10056
10057
		/**
10058
		 * Apply filter, specific for the current context plugin.
10059
		 *
10060
		 * @author Vova Feldman (@svovaf)
10061
		 * @since  1.0.9
10062
		 *
10063
		 * @param string $tag   The name of the filter hook.
10064
		 * @param mixed  $value The value on which the filters hooked to `$tag` are applied on.
10065
		 *
10066
		 * @return mixed The filtered value after all hooked functions are applied to it.
10067
		 *
10068
		 * @uses   apply_filters()
10069
		 */
10070
		function apply_filters( $tag, $value ) {
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10071
			$this->_logger->entrance( $tag );
10072
10073
			$args = func_get_args();
10074
			array_unshift( $args, $this->get_unique_affix() );
10075
10076
			return call_user_func_array( 'fs_apply_filter', $args );
10077
		}
10078
10079
		/**
10080
		 * Add filter, specific for the current context plugin.
10081
		 *
10082
		 * @author Vova Feldman (@svovaf)
10083
		 * @since  1.0.9
10084
		 *
10085
		 * @param string   $tag
10086
		 * @param callable $function_to_add
10087
		 * @param int      $priority
10088
		 * @param int      $accepted_args
10089
		 *
10090
		 * @uses   add_filter()
10091
		 */
10092
		function add_filter( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $accepted_args = 1 ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10093
			$this->_logger->entrance( $tag );
10094
10095
			add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
10096
		}
10097
10098
		/**
10099
		 * Check if has filter.
10100
		 *
10101
		 * @author Vova Feldman (@svovaf)
10102
		 * @since  1.1.4
10103
		 *
10104
		 * @param string        $tag
10105
		 * @param callable|bool $function_to_check Optional. The callback to check for. Default false.
10106
		 *
10107
		 * @return false|int
10108
		 *
10109
		 * @uses   has_filter()
10110
		 */
10111
		function has_filter( $tag, $function_to_check = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10112
			$this->_logger->entrance( $tag );
10113
10114
			return has_filter( $this->get_action_tag( $tag ), $function_to_check );
10115
		}
10116
10117
		#endregion
10118
10119
		/**
10120
		 * Override default i18n text phrases.
10121
		 *
10122
		 * @author Vova Feldman (@svovaf)
10123
		 * @since  1.1.6
10124
		 *
10125
		 * @param string[] string $key_value
10126
		 *
10127
		 * @uses   fs_override_i18n()
10128
		 */
10129
		function override_i18n( $key_value ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10130
			fs_override_i18n( $key_value, $this->_slug );
10131
		}
10132
10133
		/* Account Page
10134
		------------------------------------------------------------------------------------------------------------------*/
10135
		/**
10136
		 * Update site information.
10137
		 *
10138
		 * @author Vova Feldman (@svovaf)
10139
		 * @since  1.0.1
10140
		 *
10141
		 * @param bool $store Flush to Database if true.
10142
		 */
10143
		private function _store_site( $store = true ) {
10144
			$this->_logger->entrance();
10145
10146
			if ( empty( $this->_site->id ) ) {
10147
				$this->_logger->error( "Empty install ID, can't store site." );
10148
10149
				return;
10150
			}
10151
10152
			$encrypted_site       = clone $this->_site;
10153
			$encrypted_site->plan = self::_encrypt_entity( $this->_site->plan );
10154
10155
			$sites = self::get_all_sites( $this->_module_type );
10156
10157
            if ( empty( $this->_storage->prev_user_id ) && $this->_user->id != $this->_site->user_id ) {
0 ignored issues
show
Documentation introduced by
The property prev_user_id does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
10158
                /**
10159
                 * Store the current user ID as the previous user ID so that the previous user can be used
10160
                 * as the install's owner while the new owner's details are not yet available.
10161
                 *
10162
                 * This will be executed only in the `replica` site. For example, there are 2 sites, namely `original`
10163
                 * and `replica`, then an ownership change was initiated and completed in the `original`, the `replica`
10164
                 * will be using the previous user until it is updated again (e.g.: until the next clone of `original`
10165
                 * into `replica`.
10166
                 *
10167
                 * @author Leo Fajardo (@leorw)
10168
                 */
10169
                $this->_storage->prev_user_id = $sites[ $this->_slug ]->user_id;
0 ignored issues
show
Documentation introduced by
The property prev_user_id does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
10170
            }
10171
10172
			$sites[ $this->_slug ] = $encrypted_site;
10173
10174
			$this->set_account_option( 'sites', $sites, $store );
10175
		}
10176
10177
		/**
10178
		 * Update plugin's plans information.
10179
		 *
10180
		 * @author Vova Feldman (@svovaf)
10181
		 * @since  1.0.2
10182
		 *
10183
		 * @param bool $store Flush to Database if true.
10184
		 */
10185
		private function _store_plans( $store = true ) {
10186
			$this->_logger->entrance();
10187
10188
			$plans = self::get_all_plans( $this->_module_type );
10189
10190
			// Copy plans.
10191
			$encrypted_plans = array();
10192
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
10193
				$encrypted_plans[] = self::_encrypt_entity( $this->_plans[ $i ] );
10194
			}
10195
10196
			$plans[ $this->_slug ] = $encrypted_plans;
10197
10198
			$this->set_account_option( 'plans', $plans, $store );
10199
		}
10200
10201
		/**
10202
		 * Update user's plugin licenses.
10203
		 *
10204
		 * @author Vova Feldman (@svovaf)
10205
		 * @since  1.0.5
10206
		 *
10207
		 * @param bool                $store
10208
		 * @param string|bool         $plugin_slug
10209
		 * @param FS_Plugin_License[] $licenses
10210
		 */
10211
		private function _store_licenses( $store = true, $plugin_slug = false, $licenses = array() ) {
10212
			$this->_logger->entrance();
10213
10214
			$all_licenses = self::get_all_licenses( $this->_module_type );
10215
10216
			if ( ! is_string( $plugin_slug ) ) {
10217
				$plugin_slug = $this->_slug;
10218
				$licenses    = $this->_licenses;
10219
			}
10220
10221
			if ( ! isset( $all_licenses[ $plugin_slug ] ) ) {
10222
				$all_licenses[ $plugin_slug ] = array();
10223
			}
10224
10225
			$all_licenses[ $plugin_slug ][ $this->_user->id ] = $licenses;
10226
10227
			$this->set_account_option( 'licenses', $all_licenses, $store );
10228
		}
10229
10230
		/**
10231
		 * Update user information.
10232
		 *
10233
		 * @author Vova Feldman (@svovaf)
10234
		 * @since  1.0.1
10235
		 *
10236
		 * @param bool $store Flush to Database if true.
10237
		 */
10238
		private function _store_user( $store = true ) {
10239
			$this->_logger->entrance();
10240
10241
			if ( empty( $this->_user->id ) ) {
10242
				$this->_logger->error( "Empty user ID, can't store user." );
10243
10244
				return;
10245
			}
10246
10247
			$users                     = self::get_all_users();
10248
			$users[ $this->_user->id ] = $this->_user;
10249
			self::$_accounts->set_option( 'users', $users, $store );
10250
		}
10251
10252
		/**
10253
		 * Update new updates information.
10254
		 *
10255
		 * @author Vova Feldman (@svovaf)
10256
		 * @since  1.0.4
10257
		 *
10258
		 * @param FS_Plugin_Tag|null $update
10259
		 * @param bool               $store Flush to Database if true.
10260
		 * @param bool|number        $plugin_id
10261
		 */
10262
		private function _store_update( $update, $store = true, $plugin_id = false ) {
10263
			$this->_logger->entrance();
10264
10265
			if ( $update instanceof FS_Plugin_Tag ) {
10266
				$update->updated = time();
0 ignored issues
show
Documentation Bug introduced by
The property $updated was declared of type string, but time() is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
10267
			}
10268
10269
			if ( ! is_numeric( $plugin_id ) ) {
10270
				$plugin_id = $this->_plugin->id;
10271
			}
10272
10273
			$updates               = self::get_all_updates();
10274
			$updates[ $plugin_id ] = $update;
10275
			self::$_accounts->set_option( 'updates', $updates, $store );
10276
		}
10277
10278
		/**
10279
		 * Update new updates information.
10280
		 *
10281
		 * @author   Vova Feldman (@svovaf)
10282
		 * @since    1.0.6
10283
		 *
10284
		 * @param FS_Plugin[] $plugin_addons
10285
		 * @param bool        $store Flush to Database if true.
10286
		 */
10287
		private function _store_addons( $plugin_addons, $store = true ) {
10288
			$this->_logger->entrance();
10289
10290
			$addons                       = self::get_all_addons();
10291
			$addons[ $this->_plugin->id ] = $plugin_addons;
10292
			self::$_accounts->set_option( 'addons', $addons, $store );
10293
		}
10294
10295
		/**
10296
		 * Delete plugin's associated add-ons.
10297
		 *
10298
		 * @author   Vova Feldman (@svovaf)
10299
		 * @since    1.0.8
10300
		 *
10301
		 * @param bool $store
10302
		 *
10303
		 * @return bool
10304
		 */
10305
		private function _delete_account_addons( $store = true ) {
10306
			$all_addons = self::get_all_account_addons();
10307
10308
			if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) {
10309
				return false;
10310
			}
10311
10312
			unset( $all_addons[ $this->_plugin->id ] );
10313
10314
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
10315
10316
			return true;
10317
		}
10318
10319
		/**
10320
		 * Update account add-ons list.
10321
		 *
10322
		 * @author   Vova Feldman (@svovaf)
10323
		 * @since    1.0.6
10324
		 *
10325
		 * @param FS_Plugin[] $addons
10326
		 * @param bool        $store Flush to Database if true.
10327
		 */
10328
		private function _store_account_addons( $addons, $store = true ) {
10329
			$this->_logger->entrance();
10330
10331
			$all_addons                       = self::get_all_account_addons();
10332
			$all_addons[ $this->_plugin->id ] = $addons;
10333
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
10334
		}
10335
10336
		/**
10337
		 * Store account params in the Database.
10338
		 *
10339
		 * @author Vova Feldman (@svovaf)
10340
		 * @since  1.0.1
10341
		 */
10342
		private function _store_account() {
10343
			$this->_logger->entrance();
10344
10345
			$this->_store_site( false );
10346
			$this->_store_user( false );
10347
			$this->_store_plans( false );
10348
			$this->_store_licenses( false );
10349
10350
			self::$_accounts->store();
10351
		}
10352
10353
		/**
10354
		 * Sync user's information.
10355
		 *
10356
		 * @author Vova Feldman (@svovaf)
10357
		 * @since  1.0.3
10358
		 * @uses   FS_Api
10359
		 */
10360
		private function _handle_account_user_sync() {
10361
			$this->_logger->entrance();
10362
10363
			$api = $this->get_api_user_scope();
10364
10365
			// Get user's information.
10366
			$user = $api->get( '/', true );
10367
10368
			if ( isset( $user->id ) ) {
10369
				$this->_user->first = $user->first;
10370
				$this->_user->last  = $user->last;
10371
				$this->_user->email = $user->email;
10372
10373
				$is_menu_item_account_visible = $this->is_submenu_item_visible( 'account' );
10374
10375
				if ( $user->is_verified &&
10376
				     ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified )
10377
				) {
10378
					$this->_user->is_verified = true;
10379
10380
					$this->do_action( 'account_email_verified', $user->email );
10381
10382
					$this->_admin_notices->add(
10383
						$this->get_text( 'email-verified-message' ),
10384
						$this->get_text( 'right-on' ) . '!',
10385
						'success',
10386
						// Make admin sticky if account menu item is invisible,
10387
						// since the page will be auto redirected to the plugin's
10388
						// main settings page, and the non-sticky message
10389
						// will disappear.
10390
						! $is_menu_item_account_visible,
10391
						false,
10392
						'email_verified'
10393
					);
10394
				}
10395
10396
				// Flush user details to DB.
10397
				$this->_store_user();
10398
10399
				$this->do_action( 'after_account_user_sync', $user );
10400
10401
				/**
10402
				 * If account menu item is hidden, redirect to plugin's main settings page.
10403
				 *
10404
				 * @author Vova Feldman (@svovaf)
10405
				 * @since  1.1.6
10406
				 *
10407
				 * @link   https://github.com/Freemius/wordpress-sdk/issues/6
10408
				 */
10409
				if ( ! $is_menu_item_account_visible ) {
10410
					fs_redirect( $this->_get_admin_page_url() );
10411
				}
10412
			}
10413
		}
10414
10415
		/**
10416
		 * @param bool $store
10417
		 *
10418
		 * @return FS_Plugin_Plan|object|false
10419
		 */
10420
		private function _enrich_site_plan( $store = true ) {
10421
			// Try to load plan from local cache.
10422
			$plan = $this->_get_plan_by_id( $this->_site->plan->id );
10423
10424
			if ( false === $plan ) {
10425
				$plan = $this->_fetch_site_plan();
10426
			}
10427
10428
			if ( $plan instanceof FS_Plugin_Plan ) {
10429
				$this->_update_plan( $plan, $store );
10430
			}
10431
10432
			return $plan;
10433
		}
10434
10435
		/**
10436
		 * @author Vova Feldman (@svovaf)
10437
		 * @since  1.0.9
10438
		 * @uses   FS_Api
10439
		 *
10440
		 * @param bool $store
10441
		 *
10442
		 * @return FS_Plugin_Plan|object|false
10443
		 */
10444
		private function _enrich_site_trial_plan( $store = true ) {
10445
			// Try to load plan from local cache.
10446
			$trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id );
10447
10448
			if ( false === $trial_plan ) {
10449
				$trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id );
10450
			}
10451
10452
			if ( $trial_plan instanceof FS_Plugin_Plan ) {
10453
				$this->_storage->store( 'trial_plan', $trial_plan, $store );
10454
			}
10455
10456
			return $trial_plan;
10457
		}
10458
10459
		/**
10460
		 * @author Vova Feldman (@svovaf)
10461
		 * @since  1.0.9
10462
		 * @uses   FS_Api
10463
		 *
10464
		 * @param number|bool $license_id
10465
		 *
10466
		 * @return FS_Subscription|object|bool
10467
		 */
10468
		private function _fetch_site_license_subscription( $license_id = false ) {
10469
			$this->_logger->entrance();
10470
			$api = $this->get_api_site_scope();
10471
10472
			if ( ! is_numeric( $license_id ) ) {
10473
				$license_id = $this->_license->id;
10474
			}
10475
10476
			$result = $api->get( "/licenses/{$license_id}/subscriptions.json", true );
10477
10478
			return ! isset( $result->error ) ?
10479
				( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ?
10480
					new FS_Subscription( $result->subscriptions[0] ) :
10481
					false
10482
				) :
10483
				$result;
10484
		}
10485
10486
		/**
10487
		 * @author Vova Feldman (@svovaf)
10488
		 * @since  1.0.4
10489
		 * @uses   FS_Api
10490
		 *
10491
		 * @param number|bool $plan_id
10492
		 *
10493
		 * @return FS_Plugin_Plan|object
10494
		 */
10495
		private function _fetch_site_plan( $plan_id = false ) {
10496
			$this->_logger->entrance();
10497
			$api = $this->get_api_site_scope();
10498
10499
			if ( ! is_numeric( $plan_id ) ) {
10500
				$plan_id = $this->_site->plan->id;
10501
			}
10502
10503
			$plan = $api->get( "/plans/{$plan_id}.json", true );
10504
10505
			return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan;
10506
		}
10507
10508
		/**
10509
		 * @author Vova Feldman (@svovaf)
10510
		 * @since  1.0.5
10511
		 * @uses   FS_Api
10512
		 *
10513
		 * @return FS_Plugin_Plan[]|object
10514
		 */
10515
		private function _fetch_plugin_plans() {
10516
			$this->_logger->entrance();
10517
			$api = $this->get_api_site_scope();
10518
10519
			$result = $api->get( '/plans.json', true );
10520
10521
			if ( $this->is_api_result_object( $result, 'plans' ) && is_array( $result->plans ) ) {
10522
				for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) {
10523
					$result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] );
10524
				}
10525
10526
				$result = $result->plans;
10527
			}
10528
10529
			return $result;
10530
		}
10531
10532
		/**
10533
		 * @author Vova Feldman (@svovaf)
10534
		 * @since  1.0.5
10535
		 * @uses   FS_Api
10536
		 *
10537
		 * @param number|bool $plugin_id
10538
		 * @param number|bool $site_license_id
10539
		 *
10540
		 * @return FS_Plugin_License[]|object
10541
		 */
10542
		private function _fetch_licenses( $plugin_id = false, $site_license_id = false ) {
10543
			$this->_logger->entrance();
10544
10545
			$api = $this->get_api_user_scope();
10546
10547
			if ( ! is_numeric( $plugin_id ) ) {
10548
				$plugin_id = $this->_plugin->id;
10549
			}
10550
10551
			$result = $api->get( "/plugins/{$plugin_id}/licenses.json", true );
10552
10553
			$is_site_license_synced = false;
10554
10555
			$api_errors = array();
10556
10557
			if ( $this->is_api_result_object( $result, 'licenses' ) &&
10558
			     is_array( $result->licenses )
10559
			) {
10560
				for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) {
10561
					$result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] );
10562
10563
					if ( ( ! $is_site_license_synced ) && is_numeric( $site_license_id ) ) {
10564
						$is_site_license_synced = ( $site_license_id == $result->licenses[ $i ]->id );
10565
					}
10566
				}
10567
10568
				$result = $result->licenses;
10569
			} else {
10570
				$api_errors[] = $result;
10571
				$result       = array();
10572
			}
10573
10574
			if ( ! $is_site_license_synced ) {
10575
				$api = $this->get_api_site_scope();
10576
10577
				if ( is_numeric( $site_license_id ) ) {
10578
					// Try to retrieve a foreign license that is linked to the install.
10579
					$api_result = $api->call( '/licenses.json' );
10580
10581
					if ( $this->is_api_result_object( $api_result, 'licenses' ) &&
10582
					     is_array( $api_result->licenses )
10583
					) {
10584
						$licenses = $api_result->licenses;
10585
10586
						if ( ! empty( $licenses ) ) {
10587
							$result[] = new FS_Plugin_License( $licenses[0] );
10588
						}
10589
					} else {
10590
						$api_errors[] = $api_result;
10591
					}
10592
				} else if ( is_object( $this->_license ) ) {
10593
					// Fetch foreign license by ID and license key.
10594
					$license = $api->get( "/licenses/{$this->_license->id}.json?license_key=" .
10595
					                      urlencode( $this->_license->secret_key ) );
10596
10597
					if ( $this->is_api_result_entity( $license ) ) {
10598
						$result[] = new FS_Plugin_License( $license );
10599
					} else {
10600
						$api_errors[] = $license;
10601
					}
10602
				}
10603
			}
10604
10605
			if ( is_array( $result ) && 0 < count( $result ) ) {
10606
				// If found at least one license, return license collection even if there are errors.
10607
				return $result;
10608
			}
10609
10610
			if ( ! empty( $api_errors ) ) {
10611
				// If found any errors and no licenses, return first error.
10612
				return $api_errors[0];
10613
			}
10614
10615
			// Fallback to empty licenses list.
10616
			return $result;
10617
		}
10618
10619
		/**
10620
		 * @author Vova Feldman (@svovaf)
10621
		 * @since  1.2.0
10622
		 * @uses   FS_Api
10623
		 *
10624
		 * @param number|bool $plugin_id
10625
		 * @param bool        $flush
10626
		 *
10627
		 * @return FS_Payment[]|object
10628
		 */
10629
		function _fetch_payments( $plugin_id = false, $flush = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10630
			$this->_logger->entrance();
10631
10632
			$api = $this->get_api_user_scope();
10633
10634
			if ( ! is_numeric( $plugin_id ) ) {
10635
				$plugin_id = $this->_plugin->id;
10636
			}
10637
10638
			$result = $api->get( "/plugins/{$plugin_id}/payments.json?include_addons=true", $flush );
10639
10640
			if ( ! isset( $result->error ) ) {
10641
				for ( $i = 0, $len = count( $result->payments ); $i < $len; $i ++ ) {
10642
					$result->payments[ $i ] = new FS_Payment( $result->payments[ $i ] );
10643
				}
10644
				$result = $result->payments;
10645
			}
10646
10647
			return $result;
10648
		}
10649
10650
		/**
10651
		 * @author Vova Feldman (@svovaf)
10652
		 * @since  1.2.1.5
10653
		 * @uses   FS_Api
10654
		 *
10655
		 * @param bool $flush
10656
		 *
10657
		 * @return \FS_Billing|mixed
10658
		 */
10659
		function _fetch_billing( $flush = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10660
			require_once WP_FS__DIR_INCLUDES . '/entities/class-fs-billing.php';
10661
10662
			$billing = $this->get_api_user_scope()->get( 'billing.json', $flush );
10663
10664
			if ( $this->is_api_result_entity( $billing ) ) {
10665
				$billing = new FS_Billing( $billing );
10666
			}
10667
10668
			return $billing;
10669
		}
10670
10671
		/**
10672
		 * @author Vova Feldman (@svovaf)
10673
		 * @since  1.0.4
10674
		 *
10675
		 * @param FS_Plugin_Plan $plan
10676
		 * @param bool           $store
10677
		 */
10678
		private function _update_plan( $plan, $store = false ) {
10679
			$this->_logger->entrance();
10680
10681
			$this->_site->plan = $plan;
10682
			$this->_store_site( $store );
10683
		}
10684
10685
		/**
10686
		 * @author Vova Feldman (@svovaf)
10687
		 * @since  1.0.5
10688
		 *
10689
		 * @param FS_Plugin_License[] $licenses
10690
		 * @param string|bool         $plugin_slug
10691
		 */
10692
		private function _update_licenses( $licenses, $plugin_slug = false ) {
10693
			$this->_logger->entrance();
10694
10695
			if ( is_array( $licenses ) ) {
10696
				for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) {
10697
					$licenses[ $i ]->updated = time();
10698
				}
10699
			}
10700
10701
			if ( ! is_string( $plugin_slug ) ) {
10702
				$this->_licenses = $licenses;
10703
			}
10704
10705
			$this->_store_licenses( true, $plugin_slug, $licenses );
10706
		}
10707
10708
		/**
10709
		 * @author Vova Feldman (@svovaf)
10710
		 * @since  1.0.4
10711
		 *
10712
		 * @param bool|number $plugin_id
10713
		 * @param bool        $flush Since 1.1.7.3
10714
		 * @param int         $expiration Since 1.2.2.7
10715
		 *
10716
		 * @return object|false New plugin tag info if exist.
10717
		 */
10718
		private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
10719
			$latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration );
10720
10721
			if ( ! is_object( $latest_tag ) ) {
10722
				return false;
10723
			}
10724
10725
			// Check if version is actually newer.
10726
			$has_new_version =
10727
				// If it's an non-installed add-on then always return latest.
10728
				( $this->_is_addon_id( $plugin_id ) && ! $this->is_addon_activated( $plugin_id ) ) ||
0 ignored issues
show
Bug introduced by
It seems like $plugin_id defined by parameter $plugin_id on line 10718 can also be of type boolean; however, Freemius::is_addon_activated() does only seem to accept string|integer|double, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
10729
				// Compare versions.
10730
				version_compare( $this->get_plugin_version(), $latest_tag->version, '<' );
10731
10732
			$this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' );
10733
10734
			return $has_new_version ? $latest_tag : false;
10735
		}
10736
10737
		/**
10738
		 * @author Vova Feldman (@svovaf)
10739
		 * @since  1.0.5
10740
		 *
10741
		 * @param bool|number $plugin_id
10742
		 * @param bool        $flush      Since 1.1.7.3
10743
		 * @param int         $expiration Since 1.2.2.7
10744
		 *
10745
		 * @return bool|FS_Plugin_Tag
10746
		 */
10747
		function get_update( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10748
			$this->_logger->entrance();
10749
10750
			if ( ! is_numeric( $plugin_id ) ) {
10751
				$plugin_id = $this->_plugin->id;
10752
			}
10753
10754
			$this->check_updates( true, $plugin_id, $flush, $expiration );
10755
			$updates = $this->get_all_updates();
10756
10757
			return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
10758
		}
10759
10760
		/**
10761
		 * Check if site assigned with active license.
10762
		 *
10763
		 * @author     Vova Feldman (@svovaf)
10764
		 * @since      1.0.6
10765
		 *
10766
		 * @deprecated Please use has_active_valid_license() instead because license can be cancelled.
10767
		 */
10768
		function has_active_license() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10769
			return (
10770
				is_object( $this->_license ) &&
10771
				is_numeric( $this->_license->id ) &&
10772
				! $this->_license->is_expired()
10773
			);
10774
		}
10775
10776
		/**
10777
		 * Check if site assigned with active & valid (not expired) license.
10778
		 *
10779
		 * @author Vova Feldman (@svovaf)
10780
		 * @since  1.2.1
10781
		 */
10782
		function has_active_valid_license() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10783
			return (
10784
				is_object( $this->_license ) &&
10785
				is_numeric( $this->_license->id ) &&
10786
				$this->_license->is_active() &&
10787
				$this->_license->is_valid()
10788
			);
10789
		}
10790
10791
		/**
10792
		 * Check if site assigned with license with enabled features.
10793
		 *
10794
		 * @author Vova Feldman (@svovaf)
10795
		 * @since  1.0.6
10796
		 *
10797
		 * @return bool
10798
		 */
10799
		function has_features_enabled_license() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10800
			return (
10801
				is_object( $this->_license ) &&
10802
				is_numeric( $this->_license->id ) &&
10803
				$this->_license->is_features_enabled()
10804
			);
10805
		}
10806
10807
		/**
10808
		 * Check if user is a trial or have feature enabled license.
10809
		 *
10810
		 * @author Vova Feldman (@svovaf)
10811
		 * @since  1.1.7
10812
		 *
10813
		 * @return bool
10814
		 */
10815
		function can_use_premium_code() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10816
			return $this->is_trial() || $this->has_features_enabled_license();
10817
		}
10818
10819
		/**
10820
		 * Checks if the current user can activate plugins or switch themes. Note that this method should only be used
10821
		 * after the `init` action is triggered because it is using `current_user_can()` which is only functional after
10822
		 * the context user is authenticated.
10823
		 *
10824
		 * @author Leo Fajardo (@leorw)
10825
		 * @since  1.2.2
10826
		 *
10827
		 * @return bool
10828
		 */
10829
		function is_user_admin() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10830
			return ( $this->is_plugin() && current_user_can( 'activate_plugins' ) )
10831
			       || ( $this->is_theme() && current_user_can( 'switch_themes' ) );
10832
		}
10833
10834
		/**
10835
		 * Sync site's plan.
10836
		 *
10837
		 * @author Vova Feldman (@svovaf)
10838
		 * @since  1.0.3
10839
		 *
10840
		 * @uses   FS_Api
10841
		 *
10842
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
10843
		 *                         the admin.
10844
		 */
10845
		private function _sync_license( $background = false ) {
10846
			$this->_logger->entrance();
10847
10848
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
10849
10850
			$is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() );
10851
10852
			if ( $is_addon_sync ) {
10853
				$this->_sync_addon_license( $plugin_id, $background );
10854
			} else {
10855
				$this->_sync_plugin_license( $background );
10856
			}
10857
10858
			$this->do_action( 'after_account_plan_sync', $this->_site->plan->name );
10859
		}
10860
10861
		/**
10862
		 * Sync plugin's add-on license.
10863
		 *
10864
		 * @author Vova Feldman (@svovaf)
10865
		 * @since  1.0.6
10866
		 * @uses   FS_Api
10867
		 *
10868
		 * @param number $addon_id
10869
		 * @param bool   $background
10870
		 */
10871
		private function _sync_addon_license( $addon_id, $background ) {
10872
			$this->_logger->entrance();
10873
10874
			if ( $this->is_addon_activated( $addon_id ) ) {
10875
				// If already installed, use add-on sync.
10876
				$fs_addon = self::get_instance_by_id( $addon_id );
10877
				$fs_addon->_sync_license( $background );
10878
10879
				return;
10880
			}
10881
10882
			// Validate add-on exists.
10883
			$addon = $this->get_addon( $addon_id );
10884
10885
			if ( ! is_object( $addon ) ) {
10886
				return;
10887
			}
10888
10889
			// Add add-on into account add-ons.
10890
			$account_addons = $this->get_account_addons();
10891
			if ( ! is_array( $account_addons ) ) {
10892
				$account_addons = array();
10893
			}
10894
			$account_addons[] = $addon->id;
10895
			$account_addons   = array_unique( $account_addons );
10896
			$this->_store_account_addons( $account_addons );
0 ignored issues
show
Documentation introduced by
$account_addons is of type array<integer,integer|double>, but the function expects a array<integer,object<FS_Plugin>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
10897
10898
			// Load add-on licenses.
10899
			$licenses = $this->_fetch_licenses( $addon->id );
10900
10901
			// Sync add-on licenses.
10902
			if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) {
10903
				$this->_update_licenses( $licenses, $addon->slug );
0 ignored issues
show
Bug introduced by
It seems like $licenses defined by $this->_fetch_licenses($addon->id) on line 10899 can also be of type object; however, Freemius::_update_licenses() does only seem to accept array<integer,object<FS_Plugin_License>>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
10904
10905
				if ( ! $this->is_addon_installed( $addon->id ) && FS_License_Manager::has_premium_license( $licenses ) ) {
0 ignored issues
show
Bug introduced by
It seems like $licenses defined by $this->_fetch_licenses($addon->id) on line 10899 can also be of type object; however, FS_License_Manager::has_premium_license() does only seem to accept array<integer,object<FS_Plugin_License>>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
10906
					$plans_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$addon_id}/plans.json" );
10907
10908
					if ( ! isset( $plans_result->error ) ) {
10909
						$plans = array();
10910
						foreach ( $plans_result->plans as $plan ) {
10911
							$plans[] = new FS_Plugin_Plan( $plan );
10912
						}
10913
10914
						$this->_admin_notices->add_sticky(
10915
							FS_Plan_Manager::instance()->has_free_plan( $plans ) ?
10916
								sprintf(
10917
									$this->get_text( 'addon-successfully-upgraded-message' ),
10918
									$addon->title
10919
								) . ' ' . $this->get_latest_download_link(
10920
									$this->get_text( 'download-latest-version' ),
10921
									$addon_id
10922
								)
10923
								:
10924
								sprintf(
10925
									$this->get_text( 'addon-successfully-purchased-message' ),
10926
									$addon->title
10927
								) . ' ' . $this->get_latest_download_link(
10928
									$this->get_text( 'download-latest-version' ),
10929
									$addon_id
10930
								),
10931
							'addon_plan_upgraded_' . $addon->slug,
10932
							$this->get_text( 'yee-haw' ) . '!'
10933
						);
10934
					}
10935
				}
10936
			}
10937
		}
10938
10939
		/**
10940
		 * Sync site's plugin plan.
10941
		 *
10942
		 * @author Vova Feldman (@svovaf)
10943
		 * @since  1.0.6
10944
		 * @uses   FS_Api
10945
		 *
10946
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
10947
		 *                         the admin.
10948
		 */
10949
		private function _sync_plugin_license( $background = false ) {
10950
			$this->_logger->entrance();
10951
10952
			/**
10953
			 * Sync site info.
10954
			 *
10955
			 * @todo This line will execute install sync on a daily basis, even if running the free version (for opted-in users). The reason we want to keep it that way is for cases when the user was a paying customer, then there was a failure in subscription payment, and then after some time the payment was successful. This could be heavily optimized. For example, we can skip the $flush if the current install was never associated with a paid version.
10956
			 */
10957
			$site = $this->send_install_update( array(), true );
10958
10959
			$plan_change = 'none';
10960
10961
			if ( ! $this->is_api_result_entity( $site ) ) {
10962
				// Show API messages only if not background sync or if paying customer.
10963
				if ( ! $background || $this->is_paying() ) {
10964
					// Try to ping API to see if not blocked.
10965
					if ( ! FS_Api::test() ) {
10966
						/**
10967
						 * Failed to ping API - blocked!
10968
						 *
10969
						 * @author Vova Feldman (@svovaf)
10970
						 * @since  1.1.6 Only show message related to one of the Freemius powered plugins. Once it will be resolved it will fix the issue for all plugins anyways. There's no point to scare users with multiple error messages.
10971
						 */
10972
						$api = $this->get_api_site_scope();
10973
10974
						if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) {
10975
							self::$_global_admin_notices->add(
10976
								sprintf(
10977
									$this->get_text( 'server-blocking-access' ),
10978
									$this->get_plugin_name(),
10979
									'<a href="' . $api->get_url() . '" target="_blank">' . $api->get_url() . '</a>'
10980
								) . '<br> ' . $this->get_text( 'server-error-message' ) . var_export( $site->error, true ),
10981
								$this->get_text( 'oops' ) . '...',
10982
								'error',
10983
								$background,
10984
								false,
10985
								'api_blocked'
10986
							);
10987
						}
10988
					} else {
10989
						// Authentication params are broken.
10990
						$this->_admin_notices->add(
10991
							$this->get_text( 'wrong-authentication-param-message' ),
10992
							$this->get_text( 'oops' ) . '...',
10993
							'error'
10994
						);
10995
					}
10996
				}
10997
10998
				// No reason to continue with license sync while there are API issues.
10999
				return;
11000
			}
11001
11002
			// Remove sticky API connectivity message.
11003
			self::$_global_admin_notices->remove_sticky( 'api_blocked' );
11004
11005
			$site = new FS_Site( $site );
0 ignored issues
show
Bug introduced by
It seems like $site defined by new \FS_Site($site) on line 11005 can also be of type object or string; however, FS_Site::__construct() does only seem to accept object<stdClass>|boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
11006
11007
			// Sync plans.
11008
			$this->_sync_plans();
11009
11010
			if ( ! $this->has_paid_plan() ) {
11011
				$this->_site = $site;
11012
				$this->_enrich_site_plan( true );
11013
				$this->_store_site();
11014
			} else {
11015
				/**
11016
				 * Sync licenses. Pass the site's license ID so that the foreign licenses will be fetched if the license
11017
				 * associated with that ID is not included in the user's licenses collection.
11018
				 */
11019
				$this->_sync_licenses( $site->license_id );
11020
11021
				// Check if plan / license changed.
11022
				if ( ! FS_Entity::equals( $site->plan, $this->_site->plan ) ||
11023
				     // Check if trial started.
11024
				     $site->trial_plan_id != $this->_site->trial_plan_id ||
11025
				     $site->trial_ends != $this->_site->trial_ends ||
11026
				     // Check if license changed.
11027
				     $site->license_id != $this->_site->license_id
11028
				) {
11029
					if ( $site->is_trial() && ( ! $this->_site->is_trial() || $site->trial_ends != $this->_site->trial_ends ) ) {
11030
						// New trial started.
11031
						$this->_site = $site;
11032
						$plan_change = 'trial_started';
11033
11034
						// Store trial plan information.
11035
						$this->_enrich_site_trial_plan( true );
11036
11037
						// For trial with subscription use-case.
11038
						$new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id );
11039
11040
						if ( is_object( $new_license ) && $new_license->is_valid() ) {
11041
							$this->_site = $site;
11042
							$this->_update_site_license( $new_license );
11043
							$this->_store_licenses();
11044
							$this->_enrich_site_plan( true );
11045
11046
							$this->_sync_site_subscription( $this->_license );
11047
						}
11048
					} else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) {
11049
						// Was in trial, but now trial expired and no license ID.
11050
						// New trial started.
11051
						$this->_site = $site;
11052
						$plan_change = 'trial_expired';
11053
11054
						// Clear trial plan information.
11055
						$this->_storage->trial_plan = null;
11056
11057
					} else {
11058
						$is_free = $this->is_free_plan();
11059
11060
						// Make sure license exist and not expired.
11061
						$new_license = is_null( $site->license_id ) ?
11062
							null :
11063
							$this->_get_license_by_id( $site->license_id );
11064
11065
						if ( $is_free && is_null( $new_license ) && $this->has_any_license() && $this->_license->is_cancelled ) {
11066
							// License cancelled.
11067
							$this->_site = $site;
11068
							$this->_update_site_license( $new_license );
11069
							$this->_store_licenses();
11070
							$this->_enrich_site_plan( true );
11071
11072
							$plan_change = 'cancelled';
11073
						} else if ( $is_free && ( ( ! is_object( $new_license ) || $new_license->is_expired() ) ) ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
11074
							// The license is expired, so ignore upgrade method.
11075
						} else {
11076
							// License changed.
11077
							$this->_site = $site;
11078
							$this->_update_site_license( $new_license );
0 ignored issues
show
Security Bug introduced by
It seems like $new_license defined by is_null($site->license_i...y_id($site->license_id) on line 11061 can also be of type false; however, Freemius::_update_site_license() does only seem to accept object<FS_Plugin_License>|null, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
11079
							$this->_store_licenses();
11080
							$this->_enrich_site_plan( true );
11081
11082
							$plan_change = $is_free ?
11083
								'upgraded' :
11084
								( is_object( $new_license ) ?
11085
									'changed' :
11086
									'downgraded' );
11087
						}
11088
					}
11089
11090
					// Store updated site info.
11091
					$this->_store_site();
11092
				} else {
11093
					if ( is_object( $this->_license ) && $this->_license->is_expired() ) {
11094
						if ( ! $this->has_features_enabled_license() ) {
11095
							$this->_deactivate_license();
11096
							$plan_change = 'downgraded';
11097
						} else {
11098
							$plan_change = 'expired';
11099
						}
11100
					}
11101
11102
					if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) {
11103
						$this->_sync_site_subscription( $this->_license );
11104
					}
11105
				}
11106
			}
11107
11108
			if ( $this->has_paid_plan() ) {
11109
				switch ( $plan_change ) {
11110
					case 'none':
11111
						if ( ! $background && is_admin() ) {
11112
							$plan = $this->is_trial() ?
11113
								$this->_storage->trial_plan :
11114
								$this->_site->plan;
11115
11116
							if ( $plan->is_free() ) {
11117
								$this->_admin_notices->add(
11118
									sprintf(
11119
										$this->get_text( 'plan-did-not-change-message' ),
11120
										'<i><b>' . $plan->title . ( $this->is_trial() ? ' ' . $this->get_text( 'trial' ) : '' ) . '</b></i>'
11121
									) . ' ' . sprintf(
11122
										'<a href="%s">%s</a>',
11123
										$this->contact_url(
11124
											'bug',
11125
											sprintf( $this->get_text( 'plan-did-not-change-email-message' ),
11126
												strtoupper( $plan->name )
11127
											)
11128
										),
11129
										$this->get_text( 'contact-us-here' )
11130
									),
11131
									$this->get_text( 'hmm' ) . '...'
11132
								);
11133
							}
11134
						}
11135
						break;
11136
					case 'upgraded':
11137
						$this->_admin_notices->add_sticky(
11138
							sprintf(
11139
								$this->get_text( 'plan-upgraded-message' ),
11140
								'<i>' . $this->get_plugin_name() . '</i>'
11141
							) . $this->get_complete_upgrade_instructions(),
11142
							'plan_upgraded',
11143
							$this->get_text( 'yee-haw' ) . '!'
11144
						);
11145
11146
						$this->_admin_notices->remove_sticky( array(
11147
							'trial_started',
11148
							'trial_promotion',
11149
							'trial_expired',
11150
							'activation_complete',
11151
						) );
11152
						break;
11153
					case 'changed':
11154
						$this->_admin_notices->add_sticky(
11155
							sprintf(
11156
								$this->get_text( 'plan-changed-to-x-message' ),
11157
								$this->_site->plan->title
11158
							),
11159
							'plan_changed'
11160
						);
11161
11162
						$this->_admin_notices->remove_sticky( array(
11163
							'trial_started',
11164
							'trial_promotion',
11165
							'trial_expired',
11166
							'activation_complete',
11167
						) );
11168
						break;
11169
					case 'downgraded':
11170
						$this->_admin_notices->add_sticky(
11171
							sprintf( $this->get_text( 'license-expired-blocking-message' ), $this->_module_type ),
11172
							'license_expired',
11173
							$this->get_text( 'hmm' ) . '...'
11174
						);
11175
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
11176
						break;
11177
					case 'cancelled':
11178
						$this->_admin_notices->add(
11179
							$this->get_text( 'license-cancelled' ) . ' ' .
11180
							sprintf(
11181
								'<a href="%s">%s</a>',
11182
								$this->contact_url( 'bug' ),
11183
								$this->get_text( 'contact-us-here' )
11184
							),
11185
							$this->get_text( 'hmm' ) . '...',
11186
							'error'
11187
						);
11188
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
11189
						break;
11190
					case 'expired':
11191
						$this->_admin_notices->add_sticky(
11192
							sprintf( $this->get_text( 'license-expired-non-blocking-message' ), $this->_site->plan->title ),
11193
							'license_expired',
11194
							$this->get_text( 'hmm' ) . '...'
11195
						);
11196
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
11197
						break;
11198
					case 'trial_started':
11199
						$this->_admin_notices->add_sticky(
11200
							sprintf(
11201
								$this->get_text( 'trial-started-message' ),
11202
								'<i>' . $this->get_plugin_name() . '</i>'
11203
							) . $this->get_complete_upgrade_instructions( $this->_storage->trial_plan->title ),
11204
							'trial_started',
11205
							$this->get_text( 'yee-haw' ) . '!'
11206
						);
11207
11208
						$this->_admin_notices->remove_sticky( array(
11209
							'trial_promotion',
11210
						) );
11211
						break;
11212
					case 'trial_expired':
11213
						$this->_admin_notices->add_sticky(
11214
							$this->get_text( 'trial-expired-message' ),
11215
							'trial_expired',
11216
							$this->get_text( 'hmm' ) . '...'
11217
						);
11218
						$this->_admin_notices->remove_sticky( array(
11219
							'trial_started',
11220
							'trial_promotion',
11221
							'plan_upgraded',
11222
						) );
11223
						break;
11224
				}
11225
			}
11226
11227
			if ( 'none' !== $plan_change ) {
11228
				$this->do_action( 'after_license_change', $plan_change, $this->_site->plan );
11229
			}
11230
		}
11231
11232
		/**
11233
		 * @author Vova Feldman (@svovaf)
11234
		 * @since  1.0.5
11235
		 *
11236
		 * @param bool $background
11237
		 */
11238
		protected function _activate_license( $background = false ) {
11239
			$this->_logger->entrance();
11240
11241
			$license_id = fs_request_get( 'license_id' );
11242
11243
			if ( FS_Plugin_License::is_valid_id( $license_id ) && $license_id == $this->_site->license_id ) {
11244
				// License is already activated.
11245
				return;
11246
			}
11247
11248
			$premium_license = FS_Plugin_License::is_valid_id( $license_id ) ?
11249
				$this->_get_license_by_id( $license_id ) :
11250
				$this->_get_available_premium_license();
11251
11252
			if ( ! is_object( $premium_license ) ) {
11253
				return;
11254
			}
11255
11256
			/**
11257
			 * If the premium license is already associated with the install, just
11258
			 * update the license reference (activation is not required).
11259
			 *
11260
			 * @since 1.1.9
11261
			 */
11262
			if ( $premium_license->id == $this->_site->license_id ) {
11263
				// License is already activated.
11264
				$this->_update_site_license( $premium_license );
11265
				$this->_enrich_site_plan( false );
11266
				$this->_store_account();
11267
11268
				return;
11269
			}
11270
11271
			if ( $this->_site->user_id != $premium_license->user_id ) {
11272
				$api_request_params = array( 'license_key' => $premium_license->secret_key );
11273
			} else {
11274
				$api_request_params = array();
11275
			}
11276
11277
			$api     = $this->get_api_site_scope();
11278
			$license = $api->call( "/licenses/{$premium_license->id}.json", 'put', $api_request_params );
11279
11280
			if ( ! $this->is_api_result_entity( $license ) ) {
11281
				if ( ! $background ) {
11282
					$this->_admin_notices->add( sprintf(
11283
						'%s %s',
11284
						$this->get_text( 'license-activation-failed-message' ),
11285
						( is_object( $license ) && isset( $license->error ) ?
11286
							$license->error->message :
11287
							sprintf( '%s<br><code>%s</code>',
11288
								$this->get_text( 'server-error-message' ),
11289
								var_export( $license, true )
11290
							)
11291
						)
11292
					),
11293
						$this->get_text( 'hmm' ) . '...',
11294
						'error'
11295
					);
11296
				}
11297
11298
				return;
11299
			}
11300
11301
			$premium_license = new FS_Plugin_License( $license );
11302
11303
			// Updated site plan.
11304
			$site = $this->get_api_site_scope()->get( '/', true );
11305
			if ( $this->is_api_result_entity( $site ) ) {
11306
				$this->_site = new FS_Site( $site );
11307
			}
11308
			$this->_update_site_license( $premium_license );
11309
			$this->_enrich_site_plan( false );
11310
11311
			$this->_store_account();
11312
11313
			if ( ! $background ) {
11314
				$this->_admin_notices->add_sticky(
11315
					$this->get_text( 'license-activated-message' ) .
11316
					$this->get_complete_upgrade_instructions(),
11317
					'license_activated',
11318
					$this->get_text( 'yee-haw' ) . '!'
11319
				);
11320
			}
11321
11322
			$this->_admin_notices->remove_sticky( array(
11323
				'trial_promotion',
11324
				'license_expired',
11325
			) );
11326
		}
11327
11328
		/**
11329
		 * @author Vova Feldman (@svovaf)
11330
		 * @since  1.0.5
11331
		 *
11332
		 * @param bool $show_notice
11333
		 */
11334
		protected function _deactivate_license( $show_notice = true ) {
11335
			$this->_logger->entrance();
11336
11337
			if ( ! is_object( $this->_license ) ) {
11338
				$this->_admin_notices->add(
11339
					sprintf( $this->get_text( 'no-active-license-message' ), $this->_site->plan->title ),
11340
					$this->get_text( 'hmm' ) . '...'
11341
				);
11342
11343
				return;
11344
			}
11345
11346
			$api     = $this->get_api_site_scope();
11347
			$license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' );
11348
11349
			if ( isset( $license->error ) ) {
11350
				$this->_admin_notices->add(
11351
					$this->get_text( 'license-deactivation-failed-message' ) . '<br> ' .
11352
					$this->get_text( 'server-error-message' ) . ' ' . var_export( $license->error, true ),
11353
					$this->get_text( 'hmm' ) . '...',
11354
					'error'
11355
				);
11356
11357
				return;
11358
			}
11359
11360
			// Update license cache.
11361
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
11362
				if ( $license->id == $this->_licenses[ $i ]->id ) {
11363
					$this->_licenses[ $i ] = new FS_Plugin_License( $license );
11364
				}
11365
			}
11366
11367
			// Updated site plan to default.
11368
			$this->_sync_plans();
11369
			$this->_site->plan->id = $this->_plans[0]->id;
11370
			// Unlink license from site.
11371
			$this->_update_site_license( null );
11372
			$this->_enrich_site_plan( false );
11373
11374
			$this->_store_account();
11375
11376
			if ( $show_notice ) {
11377
				$this->_admin_notices->add(
11378
					sprintf( $this->get_text( 'license-deactivation-message' ), $this->_site->plan->title ),
11379
					$this->get_text( 'ok' )
11380
				);
11381
			}
11382
11383
			$this->_admin_notices->remove_sticky( array(
11384
				'plan_upgraded',
11385
				'license_activated',
11386
			) );
11387
		}
11388
11389
		/**
11390
		 * Site plan downgrade.
11391
		 *
11392
		 * @author Vova Feldman (@svovaf)
11393
		 * @since  1.0.4
11394
		 *
11395
		 * @uses   FS_Api
11396
		 */
11397
		private function _downgrade_site() {
11398
			$this->_logger->entrance();
11399
11400
			$api  = $this->get_api_site_scope();
11401
			$site = $api->call( 'downgrade.json', 'put' );
11402
11403
			$plan_downgraded = false;
11404
			$plan            = false;
11405
			if ( $this->is_api_result_entity( $site ) ) {
11406
				$prev_plan_id = $this->_site->plan->id;
11407
11408
				// Update new site plan id.
11409
				$this->_site->plan->id = $site->plan_id;
11410
11411
				$plan         = $this->_enrich_site_plan();
11412
				$subscription = $this->_sync_site_subscription( $this->_license );
11413
11414
				// Plan downgraded if plan was changed or subscription was cancelled.
11415
				$plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) ||
11416
				                   ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() );
11417
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
11418
				// handle different error cases.
11419
11420
			}
11421
11422
			if ( $plan_downgraded ) {
11423
				// Remove previous sticky message about upgrade (if exist).
11424
				$this->_admin_notices->remove_sticky( 'plan_upgraded' );
11425
11426
				$this->_admin_notices->add(
11427
					sprintf( $this->get_text( 'plan-x-downgraded-message' ),
11428
						$plan->title,
11429
						human_time_diff( time(), strtotime( $this->_license->expiration ) )
11430
					)
11431
				);
11432
11433
				// Store site updates.
11434
				$this->_store_site();
11435
			} else {
11436
				$this->_admin_notices->add(
11437
					$this->get_text( 'plan-downgraded-failure-message' ),
11438
					$this->get_text( 'oops' ) . '...',
11439
					'error'
11440
				);
11441
			}
11442
		}
11443
11444
		/**
11445
		 * @author Vova Feldman (@svovaf)
11446
		 * @since  1.1.8.1
11447
		 *
11448
		 * @param bool|string $plan_name
11449
		 *
11450
		 * @return bool If trial was successfully started.
11451
		 */
11452
		function start_trial( $plan_name = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
11453
			$this->_logger->entrance();
11454
11455
			if ( $this->is_trial() ) {
11456
				// Already in trial mode.
11457
				$this->_admin_notices->add(
11458
					sprintf( $this->get_text( 'in-trial-mode' ), $this->_module_type ),
11459
					$this->get_text( 'oops' ) . '...',
11460
					'error'
11461
				);
11462
11463
				return false;
11464
			}
11465
11466
			if ( $this->_site->is_trial_utilized() ) {
11467
				// Trial was already utilized.
11468
				$this->_admin_notices->add(
11469
					$this->get_text( 'trial-utilized' ),
11470
					$this->get_text( 'oops' ) . '...',
11471
					'error'
11472
				);
11473
11474
				return false;
11475
			}
11476
11477
			if ( false !== $plan_name ) {
11478
				$plan = $this->get_plan_by_name( $plan_name );
0 ignored issues
show
Bug introduced by
It seems like $plan_name defined by parameter $plan_name on line 11452 can also be of type boolean; however, Freemius::get_plan_by_name() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
11479
11480
				if ( false === $plan ) {
11481
					// Plan doesn't exist.
11482
					$this->_admin_notices->add(
11483
						sprintf( $this->get_text( 'trial-plan-x-not-exist' ), $plan_name ),
11484
						$this->get_text( 'oops' ) . '...',
11485
						'error'
11486
					);
11487
11488
					return false;
11489
				}
11490
11491
				if ( ! $plan->has_trial() ) {
11492
					// Plan doesn't exist.
11493
					$this->_admin_notices->add(
11494
						sprintf( $this->get_text( 'plan-x-no-trial' ), $plan_name ),
11495
						$this->get_text( 'oops' ) . '...',
11496
						'error'
11497
					);
11498
11499
					return false;
11500
				}
11501
			} else {
11502
				if ( ! $this->has_trial_plan() ) {
11503
					// None of the plans have a trial.
11504
					$this->_admin_notices->add(
11505
						sprintf( $this->get_text( 'no-trials' ), $this->_module_type ),
11506
						$this->get_text( 'oops' ) . '...',
11507
						'error'
11508
					);
11509
11510
					return false;
11511
				}
11512
11513
				$plans_with_trial = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
11514
11515
				$plan = $plans_with_trial[0];
11516
			}
11517
11518
			$api  = $this->get_api_site_scope();
11519
			$plan = $api->call( "plans/{$plan->id}/trials.json", 'post' );
11520
11521
			if ( ! $this->is_api_result_entity( $plan ) ) {
11522
				// Some API error while trying to start the trial.
11523
				$this->_admin_notices->add(
11524
					sprintf( $this->get_text( 'unexpected-api-error' ), $this->_module_type )
11525
					. ' ' . var_export( $plan, true ),
11526
					$this->get_text( 'oops' ) . '...',
11527
					'error'
11528
				);
11529
11530
				return false;
11531
			}
11532
11533
			// Sync license.
11534
			$this->_sync_license();
11535
11536
			return $this->is_trial();
11537
		}
11538
11539
		/**
11540
		 * Cancel site trial.
11541
		 *
11542
		 * @author Vova Feldman (@svovaf)
11543
		 * @since  1.0.9
11544
		 *
11545
		 * @uses   FS_Api
11546
		 */
11547
		private function _cancel_trial() {
11548
			$this->_logger->entrance();
11549
11550
			if ( ! $this->is_trial() ) {
11551
				$this->_admin_notices->add(
11552
					$this->get_text( 'trial-cancel-no-trial-message' ),
11553
					$this->get_text( 'oops' ) . '...',
11554
					'error'
11555
				);
11556
11557
				return;
11558
			}
11559
11560
			$api  = $this->get_api_site_scope();
11561
			$site = $api->call( 'trials.json', 'delete' );
11562
11563
			$trial_cancelled = false;
11564
11565
			if ( $this->is_api_result_entity( $site ) ) {
11566
				$prev_trial_ends = $this->_site->trial_ends;
11567
11568
				if ( $this->is_paid_trial() ) {
11569
					$this->_license->expiration   = $site->trial_ends;
11570
					$this->_license->is_cancelled = true;
11571
					$this->_update_site_license( $this->_license );
11572
					$this->_store_licenses();
11573
11574
					// Clear subscription reference.
11575
					$this->_sync_site_subscription( null );
11576
				}
11577
11578
				// Update site info.
11579
				$this->_site = new FS_Site( $site );
11580
				$this->_enrich_site_plan();
11581
11582
				$trial_cancelled = ( $prev_trial_ends != $site->trial_ends );
11583
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
11584
				// handle different error cases.
11585
11586
			}
11587
11588
			if ( $trial_cancelled ) {
11589
				// Remove previous sticky messages about upgrade or trial (if exist).
11590
				$this->_admin_notices->remove_sticky( array(
11591
					'trial_started',
11592
					'trial_promotion',
11593
					'plan_upgraded',
11594
				) );
11595
11596
				// Store site updates.
11597
				$this->_store_site();
11598
11599
				if ( ! $this->is_addon() ||
11600
				     ! $this->deactivate_premium_only_addon_without_license( true )
11601
				) {
11602
					$this->_admin_notices->add(
11603
						sprintf( $this->get_text( 'trial-cancel-message' ), $this->_storage->trial_plan->title )
11604
					);
11605
				}
11606
11607
				// Clear trial plan information.
11608
				unset( $this->_storage->trial_plan );
11609
			} else {
11610
				$this->_admin_notices->add(
11611
					$this->get_text( 'trial-cancel-failure-message' ),
11612
					$this->get_text( 'oops' ) . '...',
11613
					'error'
11614
				);
11615
			}
11616
		}
11617
11618
		/**
11619
		 * @author Vova Feldman (@svovaf)
11620
		 * @since  1.0.6
11621
		 *
11622
		 * @param bool|number $plugin_id
11623
		 *
11624
		 * @return bool
11625
		 */
11626
		private function _is_addon_id( $plugin_id ) {
11627
			return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id );
11628
		}
11629
11630
		/**
11631
		 * Check if user eligible to download premium version updates.
11632
		 *
11633
		 * @author Vova Feldman (@svovaf)
11634
		 * @since  1.0.6
11635
		 *
11636
		 * @return bool
11637
		 */
11638
		private function _can_download_premium() {
11639
			return $this->has_active_valid_license() ||
11640
			       ( $this->is_trial() && ! $this->get_trial_plan()->is_free() );
11641
		}
11642
11643
		/**
11644
		 *
11645
		 * @author Vova Feldman (@svovaf)
11646
		 * @since  1.0.6
11647
		 *
11648
		 * @param bool|number $addon_id
11649
		 * @param string      $type "json" or "zip"
11650
		 *
11651
		 * @return string
11652
		 */
11653
		private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) {
11654
11655
			$is_addon = $this->_is_addon_id( $addon_id );
11656
11657
			$is_premium = null;
11658
			if ( ! $is_addon ) {
11659
				$is_premium = $this->_can_download_premium();
11660
			} else if ( $this->is_addon_activated( $addon_id ) ) {
0 ignored issues
show
Bug introduced by
It seems like $addon_id defined by parameter $addon_id on line 11653 can also be of type boolean; however, Freemius::is_addon_activated() does only seem to accept string|integer|double, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
11661
				$is_premium = self::get_instance_by_id( $addon_id )->_can_download_premium();
0 ignored issues
show
Bug introduced by
It seems like $addon_id defined by parameter $addon_id on line 11653 can also be of type boolean; however, Freemius::get_instance_by_id() does only seem to accept integer|double, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
11662
			}
11663
11664
			// If add-on, then append add-on ID.
11665
			$endpoint = ( $is_addon ? "/addons/$addon_id" : '' ) .
11666
			            '/updates/latest.' . $type;
11667
11668
			// If add-on and not yet activated, try to fetch based on server licensing.
11669
			if ( is_bool( $is_premium ) ) {
11670
				$endpoint = add_query_arg( 'is_premium', json_encode( $is_premium ), $endpoint );
11671
			}
11672
11673
			if ( $this->has_secret_key() ) {
11674
				$endpoint = add_query_arg( 'type', 'all', $endpoint );
11675
			}
11676
11677
			return $endpoint;
11678
		}
11679
11680
		/**
11681
		 * @author Vova Feldman (@svovaf)
11682
		 * @since  1.0.4
11683
		 *
11684
		 * @param bool|number $addon_id
11685
		 * @param bool        $flush      Since 1.1.7.3
11686
		 * @param int         $expiration Since 1.2.2.7
11687
		 *
11688
		 * @return object|false Plugin latest tag info.
11689
		 */
11690
		function _fetch_latest_version( $addon_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
11691
			$this->_logger->entrance();
11692
11693
			/**
11694
			 * @since 1.1.7.3 Check for plugin updates from Freemius only if opted-in.
11695
			 * @since 1.1.7.4 Also check updates for add-ons.
11696
			 */
11697
			if ( ! $this->is_registered() &&
11698
			     ! $this->_is_addon_id( $addon_id )
11699
			) {
11700
				return false;
11701
			}
11702
11703
			$tag = $this->get_api_site_or_plugin_scope()->get(
11704
				$this->_get_latest_version_endpoint( $addon_id, 'json' ),
11705
				$flush,
11706
				$expiration
11707
			);
11708
11709
			$latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get';
11710
11711
			$this->_logger->departure( 'Latest version ' . $latest_version );
11712
11713
			return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false;
11714
		}
11715
11716
		#----------------------------------------------------------------------------------
11717
		#region Download Plugin
11718
		#----------------------------------------------------------------------------------
11719
11720
		/**
11721
		 * Download latest plugin version, based on plan.
11722
		 *
11723
		 * Not like _download_latest(), this will redirect the page
11724
		 * to secure download url to prevent dual download (from FS to WP server,
11725
		 * and then from WP server to the client / browser).
11726
		 *
11727
		 * @author Vova Feldman (@svovaf)
11728
		 * @since  1.0.9
11729
		 *
11730
		 * @param bool|number $plugin_id
11731
		 *
11732
		 * @uses   FS_Api
11733
		 * @uses   wp_redirect()
11734
		 */
11735
		private function download_latest_directly( $plugin_id = false ) {
11736
			$this->_logger->entrance();
11737
11738
			wp_redirect( $this->get_latest_download_api_url( $plugin_id ) );
11739
		}
11740
11741
		/**
11742
		 * Get latest plugin FS API download URL.
11743
		 *
11744
		 * @author Vova Feldman (@svovaf)
11745
		 * @since  1.0.9
11746
		 *
11747
		 * @param bool|number $plugin_id
11748
		 *
11749
		 * @return string
11750
		 */
11751
		private function get_latest_download_api_url( $plugin_id = false ) {
11752
			$this->_logger->entrance();
11753
11754
			return $this->get_api_site_scope()->get_signed_url(
11755
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
11756
			);
11757
		}
11758
11759
		/**
11760
		 * Get payment invoice URL.
11761
		 *
11762
		 * @author Vova Feldman (@svovaf)
11763
		 * @since  1.2.0
11764
		 *
11765
		 * @param bool|number $payment_id
11766
		 *
11767
		 * @return string
11768
		 */
11769
		function _get_invoice_api_url( $payment_id = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
11770
			$this->_logger->entrance();
11771
11772
			return $this->get_api_user_scope()->get_signed_url(
11773
				"/payments/{$payment_id}/invoice.pdf"
11774
			);
11775
		}
11776
11777
		/**
11778
		 * Get latest plugin download link.
11779
		 *
11780
		 * @author Vova Feldman (@svovaf)
11781
		 * @since  1.0.9
11782
		 *
11783
		 * @param string      $label
11784
		 * @param bool|number $plugin_id
11785
		 *
11786
		 * @return string
11787
		 */
11788
		private function get_latest_download_link( $label, $plugin_id = false ) {
11789
			return sprintf(
11790
				'<a target="_blank" href="%s">%s</a>',
11791
				$this->_get_latest_download_local_url( $plugin_id ),
11792
				$label
11793
			);
11794
		}
11795
11796
		/**
11797
		 * Get latest plugin download local URL.
11798
		 *
11799
		 * @author Vova Feldman (@svovaf)
11800
		 * @since  1.0.9
11801
		 *
11802
		 * @param bool|number $plugin_id
11803
		 *
11804
		 * @return string
11805
		 */
11806
		function _get_latest_download_local_url( $plugin_id = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
11807
			// Add timestamp to protect from caching.
11808
			$params = array( 'ts' => WP_FS__SCRIPT_START_TIME );
11809
11810
			if ( ! empty( $plugin_id ) ) {
11811
				$params['plugin_id'] = $plugin_id;
11812
			}
11813
11814
			return $this->get_account_url( 'download_latest', $params );
11815
		}
11816
11817
		#endregion Download Plugin ------------------------------------------------------------------
11818
11819
		/**
11820
		 * @author Vova Feldman (@svovaf)
11821
		 * @since  1.0.4
11822
		 *
11823
		 * @uses   FS_Api
11824
		 *
11825
		 * @param bool        $background Hints the method if it's a background updates check. If false, it means that
11826
		 *                                was initiated by the admin.
11827
		 * @param bool|number $plugin_id
11828
		 * @param bool        $flush      Since 1.1.7.3
11829
		 * @param int         $expiration Since 1.2.2.7
11830
		 */
11831
		private function check_updates(
11832
			$background = false,
11833
			$plugin_id = false,
11834
			$flush = true,
11835
			$expiration = WP_FS__TIME_24_HOURS_IN_SEC
11836
		) {
11837
			$this->_logger->entrance();
11838
11839
			// Check if there's a newer version for download.
11840
			$new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration );
11841
11842
			$update = null;
11843
			if ( is_object( $new_version ) ) {
11844
				$update = new FS_Plugin_Tag( $new_version );
0 ignored issues
show
Documentation introduced by
$new_version is of type object, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
11845
11846
				if ( ! $background ) {
11847
					$this->_admin_notices->add(
11848
						sprintf(
11849
							$this->get_text( 'version-x-released' ) . ' ' . $this->get_text( 'please-download-x' ),
11850
							$update->version,
11851
							sprintf(
11852
								'<a href="%s" target="_blank">%s</a>',
11853
								$this->get_account_url( 'download_latest' ),
11854
								sprintf( $this->get_text( 'latest-x-version' ), $this->_site->plan->title )
11855
							)
11856
						),
11857
						$this->get_text( 'new' ) . '!'
11858
					);
11859
				}
11860
			} else if ( false === $new_version && ! $background ) {
11861
				$this->_admin_notices->add(
11862
					$this->get_text( 'you-have-latest' ),
11863
					$this->get_text( 'you-are-good' )
11864
				);
11865
			}
11866
11867
			$this->_store_update( $update, true, $plugin_id );
11868
		}
11869
11870
		/**
11871
		 * @author Vova Feldman (@svovaf)
11872
		 * @since  1.0.4
11873
		 *
11874
		 * @param bool $flush Since 1.1.7.3 add 24 hour cache by default.
11875
		 *
11876
		 * @return FS_Plugin[]
11877
		 *
11878
		 * @uses   FS_Api
11879
		 */
11880
		private function sync_addons( $flush = false ) {
11881
			$this->_logger->entrance();
11882
11883
			$api = $this->get_api_site_or_plugin_scope();
11884
11885
			/**
11886
			 * @since 1.2.1
11887
			 *
11888
			 * If there's a cached version of the add-ons and not asking
11889
			 * for a flush, just use the currently stored add-ons.
11890
			 */
11891
			if ( ! $flush && $api->is_cached( '/addons.json?enriched=true' ) ) {
11892
				$addons = self::get_all_addons();
11893
11894
				return $addons[ $this->_plugin->id ];
11895
			}
11896
11897
			$result = $api->get( '/addons.json?enriched=true', $flush );
11898
11899
			$addons = array();
11900
			if ( $this->is_api_result_object( $result, 'plugins' ) &&
11901
			     is_array( $result->plugins )
11902
			) {
11903
				for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) {
11904
					$addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] );
11905
				}
11906
11907
				$this->_store_addons( $addons, true );
11908
			}
11909
11910
			return $addons;
11911
		}
11912
11913
		/**
11914
		 * Handle user email update.
11915
		 *
11916
		 * @author Vova Feldman (@svovaf)
11917
		 * @since  1.0.3
11918
		 * @uses   FS_Api
11919
		 *
11920
		 * @param string $new_email
11921
		 *
11922
		 * @return object
11923
		 */
11924
		private function update_email( $new_email ) {
11925
			$this->_logger->entrance();
11926
11927
11928
			$api  = $this->get_api_user_scope();
11929
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array(
11930
				'email'                   => $new_email,
11931
				'after_email_confirm_url' => $this->_get_admin_page_url(
11932
					'account',
11933
					array( 'fs_action' => 'sync_user' )
11934
				),
11935
			) );
11936
11937
			if ( ! isset( $user->error ) ) {
11938
				$this->_user->email       = $user->email;
11939
				$this->_user->is_verified = $user->is_verified;
11940
				$this->_store_user();
11941
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
11942
				// handle different error cases.
11943
11944
			}
11945
11946
			return $user;
11947
		}
11948
11949
		#----------------------------------------------------------------------------------
11950
		#region API Error Handling
11951
		#----------------------------------------------------------------------------------
11952
11953
		/**
11954
		 * @author Vova Feldman (@svovaf)
11955
		 * @since  1.1.1
11956
		 *
11957
		 * @param mixed $result
11958
		 *
11959
		 * @return bool Is API result contains an error.
11960
		 */
11961
		private function is_api_error( $result ) {
11962
			return FS_Api::is_api_error( $result );
11963
		}
11964
11965
		/**
11966
		 * Checks if given API result is a non-empty and not an error object.
11967
		 *
11968
		 * @author Vova Feldman (@svovaf)
11969
		 * @since  1.2.1.5
11970
		 *
11971
		 * @param mixed       $result
11972
		 * @param string|null $required_property Optional property we want to verify that is set.
11973
		 *
11974
		 * @return bool
11975
		 */
11976
		function is_api_result_object( $result, $required_property = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
11977
			return FS_Api::is_api_result_object( $result, $required_property );
11978
		}
11979
11980
		/**
11981
		 * Checks if given API result is a non-empty entity object with non-empty ID.
11982
		 *
11983
		 * @author Vova Feldman (@svovaf)
11984
		 * @since  1.2.1.5
11985
		 *
11986
		 * @param mixed $result
11987
		 *
11988
		 * @return bool
11989
		 */
11990
		private function is_api_result_entity( $result ) {
11991
			return FS_Api::is_api_result_entity( $result );
11992
		}
11993
11994
		#endregion
11995
11996
		/**
11997
		 * Make sure a given argument is an array of a specific type.
11998
		 *
11999
		 * @author Vova Feldman (@svovaf)
12000
		 * @since  1.2.1.5
12001
		 *
12002
		 * @param mixed  $array
12003
		 * @param string $class
12004
		 *
12005
		 * @return bool
12006
		 */
12007
		private function is_array_instanceof( $array, $class ) {
12008
			return ( is_array( $array ) && ( empty( $array ) || $array[0] instanceof $class ) );
12009
		}
12010
12011
		/**
12012
		 * Start install ownership change.
12013
		 *
12014
		 * @author Vova Feldman (@svovaf)
12015
		 * @since  1.1.1
12016
		 * @uses   FS_Api
12017
		 *
12018
		 * @param string $candidate_email
12019
		 *
12020
		 * @return bool Is ownership change successfully initiated.
12021
		 */
12022
		private function init_change_owner( $candidate_email ) {
12023
			$this->_logger->entrance();
12024
12025
			$api    = $this->get_api_site_scope();
12026
			$result = $api->call( "/users/{$this->_user->id}.json", 'put', array(
12027
				'email'             => $candidate_email,
12028
				'after_confirm_url' => $this->_get_admin_page_url(
12029
					'account',
12030
					array( 'fs_action' => 'change_owner' )
12031
				),
12032
			) );
12033
12034
			return ! $this->is_api_error( $result );
12035
		}
12036
12037
		/**
12038
		 * Handle install ownership change.
12039
		 *
12040
		 * @author Vova Feldman (@svovaf)
12041
		 * @since  1.1.1
12042
		 * @uses   FS_Api
12043
		 *
12044
		 * @return bool Was ownership change successfully complete.
12045
		 */
12046
		private function complete_change_owner() {
12047
			$this->_logger->entrance();
12048
12049
			$site_result = $this->get_api_site_scope( true )->get();
12050
			$site        = new FS_Site( $site_result );
12051
			$this->_site = $site;
12052
12053
			$user     = new FS_User();
12054
			$user->id = fs_request_get( 'user_id' );
12055
12056
			// Validate install's user and given user.
12057
			if ( $user->id != $this->_site->user_id ) {
12058
				return false;
12059
			}
12060
12061
			$user->public_key = fs_request_get( 'user_public_key' );
12062
			$user->secret_key = fs_request_get( 'user_secret_key' );
12063
12064
			// Fetch new user information.
12065
			$this->_user = $user;
12066
			$user_result = $this->get_api_user_scope( true )->get();
12067
			$user        = new FS_User( $user_result );
12068
			$this->_user = $user;
12069
12070
			$this->_set_account( $user, $site );
12071
12072
			return true;
12073
		}
12074
12075
		/**
12076
		 * Handle user name update.
12077
		 *
12078
		 * @author Vova Feldman (@svovaf)
12079
		 * @since  1.0.9
12080
		 * @uses   FS_Api
12081
		 *
12082
		 * @return object
12083
		 */
12084
		private function update_user_name() {
12085
			$this->_logger->entrance();
12086
			$name = fs_request_get( 'fs_user_name_' . $this->get_unique_affix(), '' );
12087
12088
			$api  = $this->get_api_user_scope();
12089
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array(
12090
				'name' => $name,
12091
			) );
12092
12093
			if ( ! isset( $user->error ) ) {
12094
				$this->_user->first = $user->first;
12095
				$this->_user->last  = $user->last;
12096
				$this->_store_user();
12097
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
12098
				// handle different error cases.
12099
12100
			}
12101
12102
			return $user;
12103
		}
12104
12105
		/**
12106
		 * Verify user email.
12107
		 *
12108
		 * @author Vova Feldman (@svovaf)
12109
		 * @since  1.0.3
12110
		 * @uses   FS_Api
12111
		 */
12112
		private function verify_email() {
12113
			$this->_handle_account_user_sync();
12114
12115
			if ( $this->_user->is_verified() ) {
12116
				return;
12117
			}
12118
12119
			$api    = $this->get_api_site_scope();
12120
			$result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array(
12121
				'after_email_confirm_url' => $this->_get_admin_page_url(
12122
					'account',
12123
					array( 'fs_action' => 'sync_user' )
12124
				)
12125
			) );
12126
12127
			if ( ! isset( $result->error ) ) {
12128
				$this->_admin_notices->add( sprintf(
12129
					$this->get_text( 'verification-email-sent-message' ),
12130
					sprintf( '<a href="mailto:%1s">%2s</a>', esc_url( $this->_user->email ), $this->_user->email )
12131
				) );
12132
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
12133
				// handle different error cases.
12134
12135
			}
12136
		}
12137
12138
		/**
12139
		 * @author Vova Feldman (@svovaf)
12140
		 * @since  1.1.2
12141
		 *
12142
		 * @param array $params
12143
		 *
12144
		 * @return string
12145
		 */
12146
		function get_activation_url( $params = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12147
			if ( $this->is_addon() && $this->has_free_plan() ) {
12148
				/**
12149
				 * @author Vova Feldman (@svovaf)
12150
				 * @since  1.2.1.7 Add-on's activation is the parent's module activation.
12151
				 */
12152
				return $this->get_parent_instance()->get_activation_url( $params );
12153
			}
12154
12155
			return $this->apply_filters( 'connect_url', $this->_get_admin_page_url( '', $params ) );
12156
		}
12157
12158
		/**
12159
		 * @author Vova Feldman (@svovaf)
12160
		 * @since  1.2.1.5
12161
		 *
12162
		 * @param array $params
12163
		 *
12164
		 * @return string
12165
		 */
12166
		function get_reconnect_url( $params = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12167
			$params['fs_action']       = 'reset_anonymous_mode';
12168
			$params['fs_unique_affix'] = $this->get_unique_affix();
12169
12170
			return $this->get_activation_url( $params );
12171
		}
12172
12173
		/**
12174
		 * Get the URL of the page that should be loaded after the user connect
12175
		 * or skip in the opt-in screen.
12176
		 *
12177
		 * @author Vova Feldman (@svovaf)
12178
		 * @since  1.1.3
12179
		 *
12180
		 * @param string $filter Filter name.
12181
		 * @param array  $params Since 1.2.2.7
12182
		 *
12183
		 * @return string
12184
		 */
12185
		function get_after_activation_url( $filter, $params = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12186
			if ( $this->is_free_wp_org_theme() &&
12187
			     fs_request_has( 'pending_activation' )
12188
			) {
12189
				$first_time_path = '';
12190
			} else {
12191
				$first_time_path = $this->_menu->get_first_time_path();
12192
			}
12193
12194
			return add_query_arg( $params, $this->apply_filters(
12195
				$filter,
12196
				empty( $first_time_path ) ?
12197
					$this->_get_admin_page_url() :
12198
					$first_time_path
12199
			) );
12200
		}
12201
12202
		/**
12203
		 * Handle account page updates / edits / actions.
12204
		 *
12205
		 * @author Vova Feldman (@svovaf)
12206
		 * @since  1.0.2
12207
		 *
12208
		 */
12209
		private function _handle_account_edits() {
12210
			if ( ! $this->is_user_admin() ) {
12211
				return;
12212
			}
12213
12214
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
12215
			$action    = fs_get_action();
12216
12217
			switch ( $action ) {
12218
				case 'delete_account':
12219
					check_admin_referer( $action );
12220
12221
					if ( $plugin_id == $this->get_id() ) {
12222
						$this->delete_account_event();
12223
12224
						// Clear user and site.
12225
						$this->_site = null;
12226
						$this->_user = null;
12227
12228
						fs_redirect( $this->get_activation_url() );
12229
					} else {
12230
						if ( $this->is_addon_activated( $plugin_id ) ) {
12231
							$fs_addon = self::get_instance_by_id( $plugin_id );
12232
							$fs_addon->delete_account_event();
12233
12234
							fs_redirect( $this->_get_admin_page_url( 'account' ) );
12235
						}
12236
					}
12237
12238
					return;
12239
12240
				case 'downgrade_account':
12241
					check_admin_referer( $action );
12242
12243
					if ( $plugin_id == $this->get_id() ) {
12244
						$this->_downgrade_site();
12245
					} else if ( $this->is_addon_activated( $plugin_id ) ) {
12246
						$fs_addon = self::get_instance_by_id( $plugin_id );
12247
						$fs_addon->_downgrade_site();
12248
					}
12249
12250
					return;
12251
12252
				case 'activate_license':
12253
					check_admin_referer( $action );
12254
12255
					if ( $plugin_id == $this->get_id() ) {
12256
						$this->_activate_license();
12257
					} else {
12258
						if ( $this->is_addon_activated( $plugin_id ) ) {
12259
							$fs_addon = self::get_instance_by_id( $plugin_id );
12260
							$fs_addon->_activate_license();
12261
						}
12262
					}
12263
12264
					return;
12265
12266
				case 'deactivate_license':
12267
					check_admin_referer( $action );
12268
12269
					if ( $plugin_id == $this->get_id() ) {
12270
						$this->_deactivate_license();
12271
12272
                        if ( $this->is_only_premium() ) {
12273
                            // Clear user and site.
12274
                            $this->_site = null;
12275
                            $this->_user = null;
12276
12277
                            fs_redirect( $this->get_activation_url() );
12278
                        }
12279
					} else {
12280
						if ( $this->is_addon_activated( $plugin_id ) ) {
12281
							$fs_addon = self::get_instance_by_id( $plugin_id );
12282
							$fs_addon->_deactivate_license();
12283
						}
12284
					}
12285
12286
					return;
12287
12288
				case 'check_updates':
12289
					check_admin_referer( $action );
12290
					$this->check_updates();
12291
12292
					return;
12293
12294
				case 'change_owner':
12295
					$state = fs_request_get( 'state', 'init' );
12296
					switch ( $state ) {
12297
						case 'init':
12298
							$candidate_email = fs_request_get( 'candidate_email', '' );
12299
12300
							if ( $this->init_change_owner( $candidate_email ) ) {
12301
								$this->_admin_notices->add( sprintf( $this->get_text( 'change-owner-request-sent-x' ), '<b>' . $this->_user->email . '</b>' ) );
12302
							}
12303
							break;
12304
						case 'owner_confirmed':
12305
							$candidate_email = fs_request_get( 'candidate_email', '' );
12306
12307
							$this->_admin_notices->add( sprintf( $this->get_text( 'change-owner-request_owner-confirmed' ), '<b>' . $candidate_email . '</b>' ) );
12308
							break;
12309
						case 'candidate_confirmed':
12310
							if ( $this->complete_change_owner() ) {
12311
								$this->_admin_notices->add_sticky(
12312
									sprintf( $this->get_text( 'change-owner-request_candidate-confirmed' ), '<b>' . $this->_user->email . '</b>' ),
12313
									'ownership_changed',
12314
									$this->get_text( 'congrats' ) . '!'
12315
								);
12316
							} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
12317
								// @todo Handle failed ownership change message.
12318
							}
12319
							break;
12320
					}
12321
12322
					return;
12323
12324
				case 'update_email':
12325
					check_admin_referer( 'update_email' );
12326
12327
					$new_email = fs_request_get( 'fs_email_' . $this->get_unique_affix(), '' );
12328
					$result    = $this->update_email( $new_email );
12329
12330
					if ( isset( $result->error ) ) {
12331
						switch ( $result->error->code ) {
12332
							case 'user_exist':
12333
								$this->_admin_notices->add(
12334
									$this->get_text( 'user-exist-message' ) . ' ' .
12335
									sprintf( $this->get_text( 'user-exist-message_ownership' ), $this->_module_type, '<b>' . $new_email . '</b>' ) .
12336
									sprintf(
12337
										'<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
12338
										$this->get_account_url( 'change_owner', array(
12339
											'state'           => 'init',
12340
											'candidate_email' => $new_email
12341
										) ),
12342
										$this->get_text( 'change-ownership' )
12343
									),
12344
									$this->get_text( 'oops' ) . '...',
12345
									'error'
12346
								);
12347
								break;
12348
						}
12349
					} else {
12350
						$this->_admin_notices->add( $this->get_text( 'email-updated-message' ) );
12351
					}
12352
12353
					return;
12354
12355
				case 'update_user_name':
12356
					check_admin_referer( 'update_user_name' );
12357
12358
					$result = $this->update_user_name();
12359
12360
					if ( isset( $result->error ) ) {
12361
						$this->_admin_notices->add(
12362
							$this->get_text( 'name-update-failed-message' ),
12363
							$this->get_text( 'oops' ) . '...',
12364
							'error'
12365
						);
12366
					} else {
12367
						$this->_admin_notices->add( $this->get_text( 'name-updated-message' ) );
12368
					}
12369
12370
					return;
12371
12372
				#region Actions that might be called from external links (e.g. email)
12373
12374
				case 'cancel_trial':
12375
					if ( $plugin_id == $this->get_id() ) {
12376
						$this->_cancel_trial();
12377
					} else {
12378
						if ( $this->is_addon_activated( $plugin_id ) ) {
12379
							$fs_addon = self::get_instance_by_id( $plugin_id );
12380
							$fs_addon->_cancel_trial();
12381
						}
12382
					}
12383
12384
					return;
12385
12386
				case 'verify_email':
12387
					$this->verify_email();
12388
12389
					return;
12390
12391
				case 'sync_user':
12392
					$this->_handle_account_user_sync();
12393
12394
					return;
12395
12396
				case $this->get_unique_affix() . '_sync_license':
12397
					$this->_sync_license();
12398
12399
					return;
12400
12401
				case 'download_latest':
12402
					$this->download_latest_directly( $plugin_id );
12403
12404
					return;
12405
12406
				#endregion
12407
			}
12408
12409
			if ( WP_FS__IS_POST_REQUEST ) {
12410
				$properties = array( 'site_secret_key', 'site_id', 'site_public_key' );
12411
				foreach ( $properties as $p ) {
12412
					if ( 'update_' . $p === $action ) {
12413
						check_admin_referer( $action );
12414
12415
						$this->_logger->log( $action );
12416
12417
						$site_property                      = substr( $p, strlen( 'site_' ) );
12418
						$site_property_value                = fs_request_get( 'fs_' . $p . '_' . $this->get_unique_affix(), '' );
12419
						$this->get_site()->{$site_property} = $site_property_value;
12420
12421
						// Store account after modification.
12422
						$this->_store_site();
12423
12424
						$this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value );
12425
12426
						$this->_admin_notices->add( sprintf(
12427
							$this->get_text( 'x-updated' ),
12428
							'<b>' . str_replace( '_', ' ', $p ) . '</b>' ) );
12429
12430
						return;
12431
					}
12432
				}
12433
			}
12434
		}
12435
12436
		/**
12437
		 * Account page resources load.
12438
		 *
12439
		 * @author Vova Feldman (@svovaf)
12440
		 * @since  1.0.6
12441
		 */
12442
		function _account_page_load() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
Coding Style introduced by
_account_page_load uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
12443
			$this->_logger->entrance();
12444
12445
			$this->_logger->info( var_export( $_REQUEST, true ) );
12446
12447
			fs_enqueue_local_style( 'fs_account', '/admin/account.css' );
12448
12449
			if ( $this->has_addons() ) {
12450
				wp_enqueue_script( 'plugin-install' );
12451
				add_thickbox();
12452
12453
				function fs_addons_body_class( $classes ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12454
					$classes .= ' plugins-php';
12455
12456
					return $classes;
12457
				}
12458
12459
				add_filter( 'admin_body_class', 'fs_addons_body_class' );
12460
			}
12461
12462
			if ( $this->has_paid_plan() &&
12463
			     ! $this->has_any_license() &&
12464
			     ! $this->is_sync_executed() &&
12465
			     $this->is_tracking_allowed()
12466
			) {
12467
				/**
12468
				 * If no licenses found and no sync job was executed during the last 24 hours,
12469
				 * just execute the sync job right away (blocking execution).
12470
				 *
12471
				 * @since 1.1.7.3
12472
				 */
12473
				$this->run_manual_sync();
12474
			}
12475
12476
			$this->_handle_account_edits();
12477
12478
			$this->do_action( 'account_page_load_before_departure' );
12479
		}
12480
12481
		/**
12482
		 * Renders the "Affiliation" page.
12483
		 *
12484
		 * @author Leo Fajardo (@leorw)
12485
		 * @since  1.2.3
12486
		 */
12487
		function _affiliation_page_render() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12488
			$this->_logger->entrance();
12489
12490
            fs_enqueue_local_style( 'fs_affiliation', '/admin/affiliation.css' );
12491
12492
            $vars = array( 'id' => $this->_module_id );
12493
			echo $this->apply_filters( "/forms/affiliation.php", fs_get_template( '/forms/affiliation.php', $vars ) );
12494
		}
12495
12496
12497
		/**
12498
		 * Render account page.
12499
		 *
12500
		 * @author Vova Feldman (@svovaf)
12501
		 * @since  1.0.0
12502
		 */
12503
		function _account_page_render() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12504
			$this->_logger->entrance();
12505
12506
			$template = 'account.php';
12507
			$vars     = array( 'id' => $this->_module_id );
12508
12509
			/**
12510
			 * Added filter to the template to allow developers wrapping the template
12511
			 * in custom HTML (e.g. within a wizard/tabs).
12512
			 *
12513
			 * @author Vova Feldman (@svovaf)
12514
			 * @since  1.2.1.6
12515
			 */
12516
			echo $this->apply_filters( "templates/{$template}", fs_get_template( $template, $vars ) );
12517
		}
12518
12519
		/**
12520
		 * Render account connect page.
12521
		 *
12522
		 * @author Vova Feldman (@svovaf)
12523
		 * @since  1.0.7
12524
		 */
12525
		function _connect_page_render() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12526
			$this->_logger->entrance();
12527
12528
			$vars = array( 'id' => $this->_module_id );
12529
12530
			/**
12531
			 * Added filter to the template to allow developers wrapping the template
12532
			 * in custom HTML (e.g. within a wizard/tabs).
12533
			 *
12534
			 * @author Vova Feldman (@svovaf)
12535
			 * @since  1.2.1.6
12536
			 */
12537
			echo $this->apply_filters( 'templates/connect.php', fs_get_template( 'connect.php', $vars ) );
12538
		}
12539
12540
		/**
12541
		 * Load required resources before add-ons page render.
12542
		 *
12543
		 * @author Vova Feldman (@svovaf)
12544
		 * @since  1.0.6
12545
		 */
12546
		function _addons_page_load() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12547
			$this->_logger->entrance();
12548
12549
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
12550
12551
			wp_enqueue_script( 'plugin-install' );
12552
			add_thickbox();
12553
12554
			function fs_addons_body_class( $classes ) {
0 ignored issues
show
Best Practice introduced by
The function fs_addons_body_class() has been defined more than once; this definition is ignored, only the first definition in this file (L12453-12457) is considered.

This check looks for functions that have already been defined in the same file.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12555
				$classes .= ' plugins-php';
12556
12557
				return $classes;
12558
			}
12559
12560
			add_filter( 'admin_body_class', 'fs_addons_body_class' );
12561
12562
			if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) {
12563
				$this->_admin_notices->add(
12564
					sprintf( $this->get_text( 'addons-info-external-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
12565
					$this->get_text( 'heads-up' ),
12566
					'update-nag'
12567
				);
12568
			}
12569
		}
12570
12571
		/**
12572
		 * Render add-ons page.
12573
		 *
12574
		 * @author Vova Feldman (@svovaf)
12575
		 * @since  1.0.6
12576
		 */
12577
		function _addons_page_render() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12578
			$this->_logger->entrance();
12579
12580
			$vars = array( 'id' => $this->_module_id );
12581
12582
			/**
12583
			 * Added filter to the template to allow developers wrapping the template
12584
			 * in custom HTML (e.g. within a wizard/tabs).
12585
			 *
12586
			 * @author Vova Feldman (@svovaf)
12587
			 * @since  1.2.1.6
12588
			 */
12589
			echo $this->apply_filters( 'templates/add-ons.php', fs_get_template( 'add-ons.php', $vars ) );
12590
		}
12591
12592
		/* Pricing & Upgrade
12593
		------------------------------------------------------------------------------------------------------------------*/
12594
		/**
12595
		 * Render pricing page.
12596
		 *
12597
		 * @author Vova Feldman (@svovaf)
12598
		 * @since  1.0.0
12599
		 */
12600
		function _pricing_page_render() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12601
			$this->_logger->entrance();
12602
12603
			$vars = array( 'id' => $this->_module_id );
12604
12605
			if ( 'true' === fs_request_get( 'checkout', false ) ) {
12606
				fs_require_once_template( 'checkout.php', $vars );
12607
			} else {
12608
				fs_require_once_template( 'pricing.php', $vars );
12609
			}
12610
		}
12611
12612
		#----------------------------------------------------------------------------------
12613
		#region Contact Us
12614
		#----------------------------------------------------------------------------------
12615
12616
		/**
12617
		 * Render contact-us page.
12618
		 *
12619
		 * @author Vova Feldman (@svovaf)
12620
		 * @since  1.0.3
12621
		 */
12622
		function _contact_page_render() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12623
			$this->_logger->entrance();
12624
12625
			$vars = array( 'id' => $this->_module_id );
12626
			fs_require_once_template( 'contact.php', $vars );
12627
		}
12628
12629
		#endregion ------------------------------------------------------------------------
12630
12631
		/**
12632
		 * Hide all admin notices to prevent distractions.
12633
		 *
12634
		 * @author Vova Feldman (@svovaf)
12635
		 * @since  1.0.3
12636
		 *
12637
		 * @uses   remove_all_actions()
12638
		 */
12639
		private static function _hide_admin_notices() {
12640
			remove_all_actions( 'admin_notices' );
12641
			remove_all_actions( 'network_admin_notices' );
12642
			remove_all_actions( 'all_admin_notices' );
12643
			remove_all_actions( 'user_admin_notices' );
12644
		}
12645
12646
		static function _clean_admin_content_section_hook() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12647
			self::_hide_admin_notices();
12648
12649
			// Hide footer.
12650
			echo '<style>#wpfooter { display: none !important; }</style>';
12651
		}
12652
12653
		/**
12654
		 * Attach to admin_head hook to hide all admin notices.
12655
		 *
12656
		 * @author Vova Feldman (@svovaf)
12657
		 * @since  1.0.3
12658
		 */
12659
		static function _clean_admin_content_section() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12660
			add_action( 'admin_head', 'Freemius::_clean_admin_content_section_hook' );
12661
		}
12662
12663
		/* CSS & JavaScript
12664
		------------------------------------------------------------------------------------------------------------------*/
12665
		/*		function _enqueue_script($handle, $src) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
12666
					$url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src );
12667
12668
					$this->_logger->entrance( 'script = ' . $url );
12669
12670
					wp_enqueue_script( $handle, $url );
12671
				}*/
12672
12673
		/* SDK
12674
		------------------------------------------------------------------------------------------------------------------*/
12675
		private $_user_api;
12676
12677
		/**
12678
		 *
12679
		 * @author Vova Feldman (@svovaf)
12680
		 * @since  1.0.2
12681
		 *
12682
		 * @param bool $flush
12683
		 *
12684
		 * @return FS_Api
12685
		 */
12686
		function get_api_user_scope( $flush = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12687
			if ( ! isset( $this->_user_api ) || $flush ) {
12688
				$this->_user_api = FS_Api::instance(
12689
					$this->_module_id,
12690
					'user',
12691
					$this->_user->id,
12692
					$this->_user->public_key,
12693
					! $this->is_live(),
12694
					$this->_user->secret_key
12695
				);
12696
			}
12697
12698
			return $this->_user_api;
12699
		}
12700
12701
		private $_site_api;
12702
12703
		/**
12704
		 *
12705
		 * @author Vova Feldman (@svovaf)
12706
		 * @since  1.0.2
12707
		 *
12708
		 * @param bool $flush
12709
		 *
12710
		 * @return FS_Api
12711
		 */
12712
		function get_api_site_scope( $flush = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12713
			if ( ! isset( $this->_site_api ) || $flush ) {
12714
				$this->_site_api = FS_Api::instance(
12715
					$this->_module_id,
12716
					'install',
12717
					$this->_site->id,
12718
					$this->_site->public_key,
12719
					! $this->is_live(),
12720
					$this->_site->secret_key
12721
				);
12722
			}
12723
12724
			return $this->_site_api;
12725
		}
12726
12727
		private $_plugin_api;
12728
12729
		/**
12730
		 * Get plugin public API scope.
12731
		 *
12732
		 * @author Vova Feldman (@svovaf)
12733
		 * @since  1.0.7
12734
		 *
12735
		 * @return FS_Api
12736
		 */
12737
		function get_api_plugin_scope() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12738
			if ( ! isset( $this->_plugin_api ) ) {
12739
				$this->_plugin_api = FS_Api::instance(
12740
					$this->_module_id,
12741
					'plugin',
12742
					$this->_plugin->id,
12743
					$this->_plugin->public_key,
12744
					! $this->is_live()
12745
				);
12746
			}
12747
12748
			return $this->_plugin_api;
12749
		}
12750
12751
		/**
12752
		 * Get site API scope object (fallback to public plugin scope when not registered).
12753
		 *
12754
		 * @author Vova Feldman (@svovaf)
12755
		 * @since  1.0.7
12756
		 *
12757
		 * @return FS_Api
12758
		 */
12759
		function get_api_site_or_plugin_scope() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12760
			return $this->is_registered() ?
12761
				$this->get_api_site_scope() :
12762
				$this->get_api_plugin_scope();
12763
		}
12764
12765
		/**
12766
		 * Show trial promotional notice (if any trial exist).
12767
		 *
12768
		 * @author Vova Feldman (@svovaf)
12769
		 * @since  1.0.9
12770
		 *
12771
		 * @param $plans
12772
		 */
12773
		function _check_for_trial_plans( $plans ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12774
			$this->_storage->has_trial_plan = FS_Plan_Manager::instance()->has_trial_plan( $plans );
12775
		}
12776
12777
		/**
12778
		 * During trial promotion the "upgrade" submenu item turns to
12779
		 * "start trial" to encourage the trial. Since we want to keep
12780
		 * the same menu item handler and there's no robust way to
12781
		 * add new arguments to the menu item link's querystring,
12782
		 * use JavaScript to find the menu item and update the href of
12783
		 * the link.
12784
		 *
12785
		 * @author Vova Feldman (@svovaf)
12786
		 * @since  1.2.1.5
12787
		 */
12788
		function _fix_start_trial_menu_item_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12789
			$template_args = array( 'id' => $this->_module_id );
12790
			fs_require_template( 'add-trial-to-pricing.php', $template_args );
12791
		}
12792
12793
		/**
12794
		 * Check if module is currently in a trial promotion mode.
12795
		 *
12796
		 * @author Vova Feldman (@svovaf)
12797
		 * @since  1.2.2.7
12798
		 *
12799
		 * @return bool
12800
		 */
12801
		function is_in_trial_promotion() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12802
			return $this->_admin_notices->has_sticky( 'trial_promotion' );
12803
		}
12804
12805
		/**
12806
		 * Show trial promotional notice (if any trial exist).
12807
		 *
12808
		 * @author Vova Feldman (@svovaf)
12809
		 * @since  1.0.9
12810
		 *
12811
		 * @return bool If trial notice added.
12812
		 */
12813
		function _add_trial_notice() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12814
			if ( ! $this->is_user_admin() ) {
12815
				return false;
12816
			}
12817
12818
			if ( ! $this->is_user_in_admin() ) {
12819
				return false;
12820
			}
12821
12822
			// Check if trial message is already shown.
12823
			if ( $this->is_in_trial_promotion() ) {
12824
				add_action( 'admin_footer', array( &$this, '_fix_start_trial_menu_item_url' ) );
12825
12826
				$this->_menu->add_counter_to_menu_item( 1, 'fs-trial' );
12827
12828
				return false;
12829
			}
12830
12831
			if ( $this->is_premium() && ! WP_FS__DEV_MODE ) {
12832
				// Don't show trial if running the premium code, unless running in DEV mode.
12833
				return false;
12834
			}
12835
12836
			if ( ! $this->has_trial_plan() ) {
12837
				// No plans with trial.
12838
				return false;
12839
			}
12840
12841
			if ( ! $this->apply_filters( 'show_trial', true ) ) {
12842
				// Developer explicitly asked not to show the trial promo.
12843
				return false;
12844
			}
12845
12846
			if ( $this->is_registered() ) {
12847
				// Check if trial already utilized.
12848
				if ( $this->_site->is_trial_utilized() ) {
12849
					return false;
12850
				}
12851
12852
				if ( $this->is_paying_or_trial() ) {
12853
					// Don't show trial if paying or already in trial.
12854
					return false;
12855
				}
12856
			}
12857
12858
			if ( $this->is_activation_mode() || $this->is_pending_activation() ) {
12859
				// If not yet opted-in/skipped, or pending activation, don't show trial.
12860
				return false;
12861
			}
12862
12863
			$last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false );
12864
			$was_promotion_shown_before      = ( false !== $last_time_trial_promotion_shown );
12865
12866
			// Show promotion if never shown before and 24 hours after initial activation with FS.
12867
			if ( ! $was_promotion_shown_before &&
12868
			     $this->_storage->install_timestamp > ( time() - WP_FS__TIME_24_HOURS_IN_SEC )
12869
			) {
12870
				return false;
12871
			}
12872
12873
			// OR if promotion was shown before, try showing it every 30 days.
12874
			if ( $was_promotion_shown_before &&
12875
			     30 * WP_FS__TIME_24_HOURS_IN_SEC > time() - $last_time_trial_promotion_shown
12876
			) {
12877
				return false;
12878
			}
12879
12880
			$trial_period    = $this->_trial_days;
12881
			$require_payment = $this->_is_trial_require_payment;
12882
			$trial_url       = $this->get_trial_url();
12883
			$plans_string    = strtolower( $this->get_text( 'awesome' ) );
12884
12885
			if ( $this->is_registered() ) {
12886
				// If opted-in, override trial with up to date data from API.
12887
				$trial_plans       = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
12888
				$trial_plans_count = count( $trial_plans );
12889
12890
				if ( 0 === $trial_plans_count ) {
12891
					// If there's no plans with a trial just exit.
12892
					return false;
12893
				}
12894
12895
				/**
12896
				 * @var FS_Plugin_Plan $paid_plan
12897
				 */
12898
				$paid_plan       = $trial_plans[0];
12899
				$require_payment = $paid_plan->is_require_subscription;
12900
				$trial_period    = $paid_plan->trial_period;
12901
12902
				$total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 );
12903
12904
				if ( $total_paid_plans !== $trial_plans_count ) {
12905
					// Not all paid plans have a trial - generate a string of those that have it.
12906
					for ( $i = 0; $i < $trial_plans_count; $i ++ ) {
12907
						$plans_string .= sprintf(
12908
							' <a href="%s">%s</a>',
12909
							$trial_url,
12910
							$trial_plans[ $i ]->title
12911
						);
12912
12913
						if ( $i < $trial_plans_count - 2 ) {
12914
							$plans_string .= ', ';
12915
						} else if ( $i == $trial_plans_count - 2 ) {
12916
							$plans_string .= ' and ';
12917
						}
12918
					}
12919
				}
12920
			}
12921
12922
			$message = sprintf(
12923
				$this->get_text( 'hey' ) . '! ' . $this->get_text( 'trial-x-promotion-message' ),
12924
				sprintf( '<b>%s</b>', $this->get_plugin_name() ),
12925
				$plans_string,
12926
				$trial_period
12927
			);
12928
12929
			// "No Credit-Card Required" or "No Commitment for N Days".
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
12930
			$cc_string = $require_payment ?
12931
				sprintf( $this->get_text( 'no-commitment-for-x-days' ), $trial_period ) :
12932
				$this->get_text( 'no-cc-required' ) . '!';
12933
12934
12935
			// Start trial button.
12936
			$button = ' ' . sprintf(
12937
					'<a style="margin-left: 10px; vertical-align: super;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
12938
					$trial_url,
12939
					$this->get_text( 'start-free-trial' )
12940
				);
12941
12942
			$this->_admin_notices->add_sticky(
12943
				$this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ),
12944
				'trial_promotion',
12945
				'',
12946
				'promotion'
12947
			);
12948
12949
			$this->_storage->trial_promotion_shown = WP_FS__SCRIPT_START_TIME;
12950
12951
			return true;
12952
		}
12953
12954
		/**
12955
		 * Lets users/customers know that the product has an affiliate program.
12956
		 *
12957
		 * @author Leo Fajardo (@leorw)
12958
		 * @since  1.2.2.11
12959
		 *
12960
		 * @return bool Returns true if the notice has been added.
12961
		 */
12962
		function _add_affiliate_program_notice() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12963
			if ( ! $this->is_user_admin() ) {
12964
				return false;
12965
			}
12966
12967
			if ( ! $this->is_user_in_admin() ) {
12968
				return false;
12969
			}
12970
12971
			// Check if the notice is already shown.
12972
			if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) {
12973
				return false;
12974
			}
12975
12976
            if (
12977
                // Product has no affiliate program.
12978
                ! $this->has_affiliate_program() ||
12979
                // User is already an affiliate.
12980
                is_object( $this->affiliate ) ||
12981
                // User has applied for an affiliate account.
12982
                ! empty( $this->_storage->affiliate_application_data ) ) {
0 ignored issues
show
Documentation introduced by
The property affiliate_application_data does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
12983
                return false;
12984
            }
12985
12986
            if ( ! $this->apply_filters( 'show_affiliate_program_notice', true ) ) {
12987
                // Developer explicitly asked not to show the notice about the affiliate program.
12988
                return false;
12989
            }
12990
12991
            if ( $this->is_activation_mode() || $this->is_pending_activation() ) {
12992
                // If not yet opted in/skipped, or pending activation, don't show the notice.
12993
                return false;
12994
            }
12995
12996
            $last_time_notice_was_shown = $this->_storage->get( 'affiliate_program_notice_shown', false );
12997
            $was_notice_shown_before    = ( false !== $last_time_notice_was_shown );
12998
12999
            /**
13000
             * Do not show the notice if it was already shown before or less than 30 days have passed since the initial
13001
             * activation with FS.
13002
             */
13003
            if ( $was_notice_shown_before ||
13004
                $this->_storage->install_timestamp > ( time() - ( WP_FS__TIME_24_HOURS_IN_SEC * 30 ) )
13005
            ) {
13006
                return false;
13007
            }
13008
13009
			if ( ! $this->is_paying() &&
13010
                FS_Plugin::AFFILIATE_MODERATION_CUSTOMERS == $this->_plugin->affiliate_moderation ) {
13011
			    // If the user is not a customer and the affiliate program is only for customers, don't show the notice.
13012
                return false;
13013
			}
13014
13015
			$message = sprintf(
13016
				$this->get_text( 'become-an-ambassador-admin-notice' ),
13017
				sprintf( '<strong>%s</strong>', $this->get_plugin_name() ),
13018
				$this->get_module_label( true )
13019
			);
13020
13021
			// HTML code for the "Learn more..." button.
13022
			$button = ' ' . sprintf(
13023
					'<a style="display: block; margin-top: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
13024
                    $this->_get_admin_page_url( 'affiliation' ),
13025
					$this->get_text( 'learn-more' ) . '...'
13026
				);
13027
13028
			$this->_admin_notices->add_sticky(
13029
				$this->apply_filters( 'affiliate_program_notice', "{$message} {$button}" ),
13030
				'affiliate_program',
13031
				'',
13032
				'promotion'
13033
			);
13034
13035
			$this->_storage->affiliate_program_notice_shown = WP_FS__SCRIPT_START_TIME;
0 ignored issues
show
Documentation introduced by
The property affiliate_program_notice_shown does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
13036
13037
			return true;
13038
		}
13039
13040
		/**
13041
		 * @author Vova Feldman (@svovaf)
13042
		 * @since  1.2.1.5
13043
		 */
13044
		function _enqueue_common_css() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13045
			if ( $this->has_paid_plan() && ! $this->is_paying() ) {
13046
				// Add basic CSS for admin-notices and menu-item colors.
13047
				fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
13048
			}
13049
		}
13050
13051
		/**
13052
		 * @author Leo Fajardo (leorw)
13053
		 * @since  1.2.2
13054
		 */
13055
		function _show_theme_activation_optin_dialog() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13056
			fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' );
13057
13058
			add_action( 'admin_footer-themes.php', array( &$this, '_add_fs_theme_activation_dialog' ) );
13059
		}
13060
13061
		/**
13062
		 * @author Leo Fajardo (leorw)
13063
		 * @since  1.2.2
13064
		 */
13065
		function _add_fs_theme_activation_dialog() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13066
			$vars = array( 'id' => $this->_module_id );
13067
			fs_require_once_template( 'connect.php', $vars );
13068
		}
13069
13070
		/* Action Links
13071
		------------------------------------------------------------------------------------------------------------------*/
13072
		private $_action_links_hooked = false;
13073
		private $_action_links = array();
13074
13075
		/**
13076
		 * Hook to plugin action links filter.
13077
		 *
13078
		 * @author Vova Feldman (@svovaf)
13079
		 * @since  1.0.0
13080
		 */
13081
		private function hook_plugin_action_links() {
13082
			$this->_logger->entrance();
13083
13084
			$this->_action_links_hooked = true;
13085
13086
			$this->_logger->log( 'Adding action links hooks.' );
13087
13088
			// Add action link to settings page.
13089
			add_filter( 'plugin_action_links_' . $this->_plugin_basename, array(
13090
				&$this,
13091
				'_modify_plugin_action_links_hook'
13092
			), WP_FS__DEFAULT_PRIORITY, 2 );
13093
			add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array(
13094
				&$this,
13095
				'_modify_plugin_action_links_hook'
13096
			), WP_FS__DEFAULT_PRIORITY, 2 );
13097
		}
13098
13099
		/**
13100
		 * Add plugin action link.
13101
		 *
13102
		 * @author Vova Feldman (@svovaf)
13103
		 * @since  1.0.0
13104
		 *
13105
		 * @param      $label
13106
		 * @param      $url
13107
		 * @param bool $external
13108
		 * @param int  $priority
13109
		 * @param bool $key
13110
		 */
13111
		function add_plugin_action_link( $label, $url, $external = false, $priority = WP_FS__DEFAULT_PRIORITY, $key = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13112
			$this->_logger->entrance();
13113
13114
			if ( ! isset( $this->_action_links[ $priority ] ) ) {
13115
				$this->_action_links[ $priority ] = array();
13116
			}
13117
13118
			if ( false === $key ) {
13119
				$key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) );
13120
			}
13121
13122
			$this->_action_links[ $priority ][] = array(
13123
				'label'    => $label,
13124
				'href'     => $url,
13125
				'key'      => $key,
13126
				'external' => $external
13127
			);
13128
		}
13129
13130
		/**
13131
		 * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection.
13132
		 *
13133
		 * @author Vova Feldman (@svovaf)
13134
		 * @since  1.0.0
13135
		 */
13136
		function _add_upgrade_action_link() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13137
			$this->_logger->entrance();
13138
13139
			if ( $this->is_registered() ) {
13140
				if ( ! $this->is_paying() && $this->has_paid_plan() ) {
13141
					$this->add_plugin_action_link(
13142
						$this->get_text( 'upgrade' ),
13143
						$this->get_upgrade_url(),
13144
						false,
13145
						7,
13146
						'upgrade'
0 ignored issues
show
Documentation introduced by
'upgrade' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
13147
					);
13148
				}
13149
13150
				if ( $this->has_addons() ) {
13151
					$this->add_plugin_action_link(
13152
						$this->get_text( 'add-ons' ),
13153
						$this->_get_admin_page_url( 'addons' ),
13154
						false,
13155
						9,
13156
						'addons'
0 ignored issues
show
Documentation introduced by
'addons' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
13157
					);
13158
				}
13159
			}
13160
		}
13161
13162
		/**
13163
		 * Adds "Activate License" or "Change License" link to the main Plugins page link actions collection.
13164
		 *
13165
		 * @author Leo Fajardo (@leorw)
13166
		 * @since  1.1.9
13167
		 */
13168
		function _add_license_action_link() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13169
			$this->_logger->entrance();
13170
13171
			if ( $this->is_free_plan() && $this->is_addon() ) {
13172
				return;
13173
			}
13174
13175
			if ( ! self::is_ajax() ) {
13176
				// Inject license activation dialog UI and client side code.
13177
				add_action( 'admin_footer', array( &$this, '_add_license_activation_dialog_box' ) );
13178
			}
13179
13180
			$link_text = $this->get_text(
13181
				$this->is_free_plan() ?
13182
					'activate-license' :
13183
					'change-license'
13184
			);
13185
13186
			$this->add_plugin_action_link(
13187
				$link_text,
13188
				'#',
13189
				false,
13190
				11,
13191
				( 'activate-license ' . $this->get_unique_affix() )
0 ignored issues
show
Documentation introduced by
'activate-license ' . $this->get_unique_affix() is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
13192
			);
13193
		}
13194
13195
		/**
13196
		 * Adds "Opt in" or "Opt out" link to the main "Plugins" page link actions collection.
13197
		 *
13198
		 * @author Leo Fajardo (@leorw)
13199
		 * @since  1.2.1.5
13200
		 */
13201
		function _add_tracking_links() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13202
			if ( ! current_user_can( 'activate_plugins' ) ) {
13203
				return;
13204
			}
13205
13206
			$this->_logger->entrance();
13207
13208
			if ( fs_request_is_action_secure( $this->get_unique_affix() . '_reconnect' ) ) {
13209
				if ( ! $this->is_registered() && $this->is_anonymous() ) {
13210
					$this->connect_again();
13211
13212
					return;
13213
				}
13214
			}
13215
13216
			if ( ( $this->is_plugin() && ! self::is_plugins_page() ) ||
13217
			     ( $this->is_theme() && ! self::is_themes_page() )
13218
			) {
13219
				// Only show tracking links on the plugins and themes pages.
13220
				return;
13221
			}
13222
13223
			if ( ! $this->is_enable_anonymous() ) {
13224
				// Don't allow to opt-out if anonymous mode is disabled.
13225
				return;
13226
			}
13227
13228
			if ( ! $this->is_free_plan() ) {
13229
				// Don't allow to opt-out if running in paid plan.
13230
				return;
13231
			}
13232
13233
			if ( $this->add_ajax_action( 'stop_tracking', array( &$this, '_stop_tracking_callback' ) ) ) {
13234
				return;
13235
			}
13236
13237
			if ( $this->add_ajax_action( 'allow_tracking', array( &$this, '_allow_tracking_callback' ) ) ) {
13238
				return;
13239
			}
13240
13241
			$url = '#';
13242
13243
			if ( $this->is_registered() ) {
13244
				if ( $this->is_tracking_allowed() ) {
13245
					$link_text_id = 'opt-out';
13246
				} else {
13247
					$link_text_id = 'opt-in';
13248
				}
13249
13250
				add_action( 'admin_footer', array( &$this, '_add_optout_dialog' ) );
13251
			} else {
13252
				$link_text_id = 'opt-in';
13253
13254
				$params = ! $this->is_anonymous() ?
13255
					array() :
13256
					array(
13257
						'nonce'     => wp_create_nonce( $this->get_unique_affix() . '_reconnect' ),
13258
						'fs_action' => ( $this->get_unique_affix() . '_reconnect' ),
13259
					);
13260
13261
				$url = $this->get_activation_url( $params );
13262
			}
13263
13264
			if ( $this->is_plugin() && self::is_plugins_page() ) {
13265
				$this->add_plugin_action_link(
13266
					$this->get_text( $link_text_id ),
13267
					$url,
13268
					false,
13269
					13,
13270
					"opt-in-or-opt-out {$this->_slug}"
13271
				);
13272
			}
13273
		}
13274
13275
		/**
13276
		 * Get the URL of the page that should be loaded right after the plugin activation.
13277
		 *
13278
		 * @author Vova Feldman (@svovaf)
13279
		 * @since  1.1.7.4
13280
		 *
13281
		 * @return string
13282
		 */
13283
		function get_after_plugin_activation_redirect_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13284
			$url       = false;
13285
13286
			if ( ! $this->is_addon() || ! $this->has_free_plan() ) {
13287
				$first_time_path = $this->_menu->get_first_time_path();
13288
				$url             = $this->is_activation_mode() ?
13289
					$this->get_activation_url() :
13290
					( empty( $first_time_path ) ?
13291
						$this->_get_admin_page_url() :
13292
						$first_time_path );
13293
			} else {
13294
				$plugin_fs = false;
13295
13296
				if ( $this->is_parent_plugin_installed() ) {
13297
					$plugin_fs = self::get_parent_instance();
13298
				}
13299
13300
				if ( is_object( $plugin_fs ) ) {
13301
					if ( ! $plugin_fs->is_registered() ) {
13302
						// Forward to parent plugin connect when parent not registered.
13303
						$url = $plugin_fs->get_activation_url();
13304
					} else {
13305
						// Forward to account page.
13306
						$url = $plugin_fs->_get_admin_page_url( 'account' );
13307
					}
13308
				}
13309
			}
13310
13311
			return $url;
13312
		}
13313
13314
		/**
13315
		 * Forward page to activation page.
13316
		 *
13317
		 * @author Vova Feldman (@svovaf)
13318
		 * @since  1.0.3
13319
		 */
13320
		function _redirect_on_activation_hook() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13321
			$url = $this->get_after_plugin_activation_redirect_url();
13322
13323
			if ( is_string( $url ) ) {
13324
				fs_redirect( $url );
13325
			}
13326
		}
13327
13328
		/**
13329
		 * Modify plugin's page action links collection.
13330
		 *
13331
		 * @author Vova Feldman (@svovaf)
13332
		 * @since  1.0.0
13333
		 *
13334
		 * @param array $links
13335
		 * @param       $file
13336
		 *
13337
		 * @return array
13338
		 */
13339
		function _modify_plugin_action_links_hook( $links, $file ) {
0 ignored issues
show
Unused Code introduced by
The parameter $file is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13340
			$this->_logger->entrance();
13341
13342
			$passed_deactivate = false;
13343
			$deactivate_link   = '';
13344
			$before_deactivate = array();
13345
			$after_deactivate  = array();
13346
			foreach ( $links as $key => $link ) {
13347
				if ( 'deactivate' === $key ) {
13348
					$deactivate_link   = $link;
13349
					$passed_deactivate = true;
13350
					continue;
13351
				}
13352
13353
				if ( ! $passed_deactivate ) {
13354
					$before_deactivate[ $key ] = $link;
13355
				} else {
13356
					$after_deactivate[ $key ] = $link;
13357
				}
13358
			}
13359
13360
			ksort( $this->_action_links );
13361
13362
			foreach ( $this->_action_links as $new_links ) {
13363
				foreach ( $new_links as $link ) {
13364
					$before_deactivate[ $link['key'] ] = '<a href="' . $link['href'] . '"' . ( $link['external'] ? ' target="_blank"' : '' ) . '>' . $link['label'] . '</a>';
13365
				}
13366
			}
13367
13368
			if ( ! empty( $deactivate_link ) ) {
13369
				/**
13370
				 * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link.
13371
				 *
13372
				 * @since 1.2.1.6 Always show the deactivation feedback form since we added automatic free version deactivation upon premium code activation.
13373
				 */
13374
				$deactivate_link .= '<i class="fs-module-id" data-module-id="' . $this->_module_id . '"></i>';
13375
13376
				// Append deactivation link.
13377
				$before_deactivate['deactivate'] = $deactivate_link;
13378
			}
13379
13380
			return array_merge( $before_deactivate, $after_deactivate );
13381
		}
13382
13383
		/**
13384
		 * Adds admin message.
13385
		 *
13386
		 * @author Vova Feldman (@svovaf)
13387
		 * @since  1.0.4
13388
		 *
13389
		 * @param string $message
13390
		 * @param string $title
13391
		 * @param string $type
13392
		 */
13393
		function add_admin_message( $message, $title = '', $type = 'success' ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13394
			$this->_admin_notices->add( $message, $title, $type );
13395
		}
13396
13397
		/**
13398
		 * Adds sticky admin message.
13399
		 *
13400
		 * @author Vova Feldman (@svovaf)
13401
		 * @since  1.1.0
13402
		 *
13403
		 * @param string $message
13404
		 * @param string $id
13405
		 * @param string $title
13406
		 * @param string $type
13407
		 */
13408
		function add_sticky_admin_message( $message, $id, $title = '', $type = 'success' ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13409
			$this->_admin_notices->add_sticky( $message, $id, $title, $type );
13410
		}
13411
13412
		/**
13413
		 * Helper function that returns the final steps for the upgrade completion.
13414
		 *
13415
		 * If the module is already running the premium code, returns an empty string.
13416
		 *
13417
		 * @author Vova Feldman (@svovaf)
13418
		 * @since  1.2.1
13419
		 *
13420
		 * @param string $plan_title
13421
		 *
13422
		 * @return string
13423
		 */
13424
		private function get_complete_upgrade_instructions( $plan_title = '' ) {
13425
			if ( ! $this->has_premium_version() || $this->is_premium() ) {
13426
				return '';
13427
			}
13428
13429
			if ( empty( $plan_title ) ) {
13430
				$plan_title = $this->_site->plan->title;
13431
			}
13432
13433
			// @since 1.2.1.5 The free version is auto deactivated.
13434
			$deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ?
13435
				( '<li>' . $this->get_text( 'deactivate-free-version' ) . '.</li>' ) :
13436
				'';
13437
13438
			return sprintf(
13439
				' %s: <ol><li>%s.</li>%s<li>%s (<a href="%s" target="_blank">%s</a>).</li></ol>',
13440
				$this->get_text( 'follow-steps-to-complete-upgrade' ),
13441
				$this->get_latest_download_link( sprintf(
13442
					$this->get_text( 'download-latest-x-version' ),
13443
					$plan_title
13444
				) ),
13445
				$deactivation_step,
13446
				$this->get_text( 'upload-and-activate' ),
13447
				'//bit.ly/upload-wp-' . $this->_module_type . 's',
13448
				$this->get_text( 'howto-upload-activate' )
13449
			);
13450
		}
13451
13452
		/**
13453
		 * @author Vova Feldman (@svovaf)
13454
		 * @since  1.2.1.7
13455
		 *
13456
		 * @param string $key
13457
		 *
13458
		 * @return string
13459
		 */
13460
		function get_text( $key ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13461
			return fs_text( $key, $this->_slug );
13462
		}
13463
13464
		#----------------------------------------------------------------------------------
13465
		#region Versioning
13466
		#----------------------------------------------------------------------------------
13467
13468
		/**
13469
		 * Check if Freemius in SDK upgrade mode.
13470
		 *
13471
		 * @author Vova Feldman (@svovaf)
13472
		 * @since  1.0.9
13473
		 *
13474
		 * @return bool
13475
		 */
13476
		function is_sdk_upgrade_mode() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13477
			return isset( $this->_storage->sdk_upgrade_mode ) ?
13478
				$this->_storage->sdk_upgrade_mode :
13479
				false;
13480
		}
13481
13482
		/**
13483
		 * Turn SDK upgrade mode off.
13484
		 *
13485
		 * @author Vova Feldman (@svovaf)
13486
		 * @since  1.0.9
13487
		 */
13488
		function set_sdk_upgrade_complete() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13489
			$this->_storage->sdk_upgrade_mode = false;
13490
		}
13491
13492
		/**
13493
		 * Check if plugin upgrade mode.
13494
		 *
13495
		 * @author Vova Feldman (@svovaf)
13496
		 * @since  1.0.9
13497
		 *
13498
		 * @return bool
13499
		 */
13500
		function is_plugin_upgrade_mode() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13501
			return isset( $this->_storage->plugin_upgrade_mode ) ?
13502
				$this->_storage->plugin_upgrade_mode :
13503
				false;
13504
		}
13505
13506
		/**
13507
		 * Turn plugin upgrade mode off.
13508
		 *
13509
		 * @author Vova Feldman (@svovaf)
13510
		 * @since  1.0.9
13511
		 */
13512
		function set_plugin_upgrade_complete() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13513
			$this->_storage->plugin_upgrade_mode = false;
13514
		}
13515
13516
		#endregion
13517
13518
		#----------------------------------------------------------------------------------
13519
		#region Permissions
13520
		#----------------------------------------------------------------------------------
13521
13522
		/**
13523
		 * Check if specific permission requested.
13524
		 *
13525
		 * @author Vova Feldman (@svovaf)
13526
		 * @since  1.1.6
13527
		 *
13528
		 * @param string $permission
13529
		 *
13530
		 * @return bool
13531
		 */
13532
		function is_permission_requested( $permission ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13533
			return isset( $this->_permissions[ $permission ] ) && ( true === $this->_permissions[ $permission ] );
13534
		}
13535
13536
		#endregion
13537
13538
		#----------------------------------------------------------------------------------
13539
		#region Auto Activation
13540
		#----------------------------------------------------------------------------------
13541
13542
		/**
13543
		 * Hints the SDK if running an auto-installation.
13544
		 *
13545
		 * @var bool
13546
		 */
13547
		private $_isAutoInstall = false;
13548
13549
		/**
13550
		 * After upgrade callback to install and auto activate a plugin.
13551
		 * This code will only be executed on explicit request from the user,
13552
		 * following the practice Jetpack are using with their theme installations.
13553
		 *
13554
		 * @link   https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
13555
		 *
13556
		 * @author Vova Feldman (@svovaf)
13557
		 * @since  1.2.1.7
13558
		 */
13559
		function _install_premium_version_ajax_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13560
			$this->_logger->entrance();
13561
13562
			$this->check_ajax_referer( 'install_premium_version' );
13563
13564
			if ( ! $this->is_registered() ) {
13565
				// Not registered.
13566
				self::shoot_ajax_failure( array(
13567
					'message' => $this->get_text( 'auto-install-error-not-opted-in' ),
13568
					'code'    => 'premium_installed',
13569
				) );
13570
			}
13571
13572
			$plugin_id = fs_request_get( 'module_id', $this->get_id() );
13573
13574
			if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) {
13575
				// Invalid ID.
13576
				self::shoot_ajax_failure( array(
13577
					'message' => $this->get_text( 'auto-install-error-invalid-id' ),
13578
					'code'    => 'invalid_module_id',
13579
				) );
13580
			}
13581
13582
			if ( $plugin_id == $this->get_id() ) {
13583
				if ( $this->is_premium() ) {
13584
					// Already using the premium code version.
13585
					self::shoot_ajax_failure( array(
13586
						'message' => $this->get_text( 'auto-install-error-premium-activated' ),
13587
						'code'    => 'premium_installed',
13588
					) );
13589
				}
13590
				if ( ! $this->can_use_premium_code() ) {
13591
					// Don't have access to the premium code.
13592
					self::shoot_ajax_failure( array(
13593
						'message' => $this->get_text( 'auto-install-error-invalid-license' ),
13594
						'code'    => 'invalid_license',
13595
					) );
13596
				}
13597
				if ( ! $this->has_release_on_freemius() ) {
13598
					// Plugin is a serviceware, no premium code version.
13599
					self::shoot_ajax_failure( array(
13600
						'message' => $this->get_text( 'auto-install-error-serviceware' ),
13601
						'code'    => 'premium_version_missing',
13602
					) );
13603
				}
13604
			} else {
13605
				$addon = $this->get_addon( $plugin_id );
13606
13607
				if ( ! is_object( $addon ) ) {
13608
					// Invalid add-on ID.
13609
					self::shoot_ajax_failure( array(
13610
						'message' => $this->get_text( 'auto-install-error-invalid-id' ),
13611
						'code'    => 'invalid_module_id',
13612
					) );
13613
				}
13614
13615
				if ( $this->is_addon_activated( $plugin_id, true ) ) {
13616
					// Premium add-on version is already activated.
13617
					self::shoot_ajax_failure( array(
13618
						'message' => $this->get_text( 'auto-install-error-premium-addon-activated' ),
13619
						'code'    => 'premium_installed',
13620
					) );
13621
				}
13622
			}
13623
13624
			$this->_isAutoInstall = true;
13625
13626
			// Try to install and activate.
13627
			$updater = new FS_Plugin_Updater( $this );
13628
			$result  = $updater->install_and_activate_plugin( $plugin_id );
13629
13630
			if ( is_array( $result ) && ! empty( $result['message'] ) ) {
13631
				self::shoot_ajax_failure( array(
13632
					'message' => $result['message'],
13633
					'code'    => $result['code'],
13634
				) );
13635
			}
13636
13637
			self::shoot_ajax_success( $result );
13638
		}
13639
13640
		/**
13641
		 * Displays module activation dialog box after a successful upgrade
13642
		 * where the user explicitly requested to auto download and install
13643
		 * the premium version.
13644
		 *
13645
		 * @author Vova Feldman (@svovaf)
13646
		 * @since  1.2.1.7
13647
		 */
13648
		function _add_auto_installation_dialog_box() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13649
			$this->_logger->entrance();
13650
13651
			if ( ! $this->is_registered() ) {
13652
				// Not registered.
13653
				return;
13654
			}
13655
13656
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
13657
13658
			if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) {
13659
				// Invalid module ID.
13660
				return;
13661
			}
13662
13663
			if ( $plugin_id == $this->get_id() ) {
13664
				if ( $this->is_premium() ) {
13665
					// Already using the premium code version.
13666
					return;
13667
				}
13668
				if ( ! $this->can_use_premium_code() ) {
13669
					// Don't have access to the premium code.
13670
					return;
13671
				}
13672
				if ( ! $this->has_release_on_freemius() ) {
13673
					// Plugin is a serviceware, no premium code version.
13674
					return;
13675
				}
13676
			} else {
13677
				$addon = $this->get_addon( $plugin_id );
13678
13679
				if ( ! is_object( $addon ) ) {
13680
					// Invalid add-on ID.
13681
					return;
13682
				}
13683
13684
				if ( $this->is_addon_activated( $plugin_id, true ) ) {
13685
					// Premium add-on version is already activated.
13686
					return;
13687
				}
13688
			}
13689
13690
			$vars = array(
13691
				'id'   => $plugin_id,
13692
				'slug' => $this->_slug,
13693
			);
13694
13695
			fs_require_template( 'auto-installation.php', $vars );
13696
		}
13697
13698
		#endregion
13699
13700
		#--------------------------------------------------------------------------------
13701
		#region Tabs Integration
13702
		#--------------------------------------------------------------------------------
13703
13704
		#region Module's Original Tabs
13705
13706
		/**
13707
		 * Inject a JavaScript logic to capture the theme tabs HTML.
13708
		 *
13709
		 * @author Vova Feldman (@svovaf)
13710
		 * @since  1.2.2.7
13711
		 */
13712
		function _tabs_capture() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13713
			$this->_logger->entrance();
13714
13715
			if ( ! $this->is_theme_settings_page() ||
13716
			     ! $this->is_matching_url( $this->main_menu_url() )
13717
			) {
13718
				return;
13719
			}
13720
13721
			$params = array(
13722
				'id' => $this->_module_id,
13723
			);
13724
13725
			fs_require_once_template( 'tabs-capture-js.php', $params );
13726
		}
13727
13728
		/**
13729
		 * Cache theme's tabs HTML for a week. The cache will also be set as expired
13730
		 * after version and type (free/premium) changes, in addition to the week period.
13731
		 *
13732
		 * @author Vova Feldman (@svovaf)
13733
		 * @since  1.2.2.7
13734
		 */
13735
		function _store_tabs_ajax_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13736
			$this->_logger->entrance();
13737
13738
			$this->check_ajax_referer( 'store_tabs' );
13739
13740
			// Init filesystem if not yet initiated.
13741
			WP_Filesystem();
13742
13743
			// Get POST body HTML data.
13744
			global $wp_filesystem;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
13745
			$tabs_html = $wp_filesystem->get_contents( "php://input" );
13746
13747
			if ( is_string( $tabs_html ) ) {
13748
				$tabs_html = trim( $tabs_html );
13749
			}
13750
13751
			if ( ! is_string( $tabs_html ) || empty( $tabs_html ) ) {
13752
				self::shoot_ajax_failure();
13753
			}
13754
13755
			$this->_cache->set( 'tabs', $tabs_html, 7 * WP_FS__TIME_24_HOURS_IN_SEC );
13756
13757
			self::shoot_ajax_success();
13758
		}
13759
13760
		/**
13761
		 * Cache theme's settings page custom styles. The cache will also be set as expired
13762
		 * after version and type (free/premium) changes, in addition to the week period.
13763
		 *
13764
		 * @author Vova Feldman (@svovaf)
13765
		 * @since  1.2.2.7
13766
		 */
13767
		function _store_tabs_styles() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13768
			$this->_logger->entrance();
13769
13770
			if ( ! $this->is_theme_settings_page() ||
13771
			     ! $this->is_matching_url( $this->main_menu_url() )
13772
			) {
13773
				return;
13774
			}
13775
13776
			$wp_styles = wp_styles();
13777
13778
			$theme_styles_url = get_template_directory_uri();
13779
13780
			$stylesheets = array();
13781
			foreach ( $wp_styles->queue as $handler ) {
13782
				if ( fs_starts_with( $handler, 'fs_' ) ) {
13783
					// Assume that stylesheets that their handler starts with "fs_" belong to the SDK.
13784
					continue;
13785
				}
13786
13787
				/**
13788
				 * @var _WP_Dependency $stylesheet
13789
				 */
13790
				$stylesheet = $wp_styles->registered[ $handler ];
13791
13792
				if ( fs_starts_with( $stylesheet->src, $theme_styles_url ) ) {
13793
					$stylesheets[] = $stylesheet->src;
13794
				}
13795
			}
13796
13797
			if ( ! empty( $stylesheets ) ) {
13798
				$this->_cache->set( 'tabs_stylesheets', $stylesheets, 7 * WP_FS__TIME_24_HOURS_IN_SEC );
13799
			}
13800
		}
13801
13802
		/**
13803
		 * Check if module's original settings page has any tabs.
13804
		 *
13805
		 * @author Vova Feldman (@svovaf)
13806
		 * @since  1.2.2.7
13807
		 *
13808
		 * @return bool
13809
		 */
13810
		private function has_tabs() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
13811
			return $this->_cache->has( 'tabs' );
13812
		}
13813
13814
		/**
13815
		 * Get module's settings page HTML content, starting
13816
		 * from the beginning of the <div class="wrap"> element,
13817
		 * until the tabs HTML (including).
13818
		 *
13819
		 * @author Vova Feldman (@svovaf)
13820
		 * @since  1.2.2.7
13821
		 *
13822
		 * @return string
13823
		 */
13824
		private function get_tabs_html() {
13825
			$this->_logger->entrance();
13826
13827
			return $this->_cache->get( 'tabs' );
13828
		}
13829
13830
		/**
13831
		 * Check if page should include tabs.
13832
		 *
13833
		 * @author Vova Feldman (@svovaf)
13834
		 * @since  1.2.2.7
13835
		 *
13836
		 * @return bool
13837
		 */
13838
		private function should_page_include_tabs()
13839
		{
13840
			if ( ! $this->has_settings_menu() ) {
13841
				// Don't add tabs if no settings at all.
13842
				return false;
13843
			}
13844
13845
			if ( ! $this->is_theme() ) {
13846
				// Only add tabs to themes for now.
13847
				return false;
13848
			}
13849
13850
			if ( ! $this->has_paid_plan() && ! $this->has_addons() ) {
13851
				// Only add tabs to monetizing themes.
13852
				return false;
13853
			}
13854
13855
			if ( ! $this->is_theme_settings_page() ) {
13856
				// Only add tabs if browsing one of the theme's setting pages.
13857
				return false;
13858
			}
13859
13860
			if ( $this->is_admin_page( 'pricing' ) && fs_request_get_bool( 'checkout' ) ) {
13861
				// Don't add tabs on checkout page, we want to reduce distractions
13862
				// as much as possible.
13863
				return false;
13864
			}
13865
13866
			return true;
13867
		}
13868
13869
		/**
13870
		 * Add the tabs HTML before the setting's page content and
13871
		 * enqueue any required stylesheets.
13872
		 *
13873
		 * @author Vova Feldman (@svovaf)
13874
		 * @since  1.2.2.7
13875
		 *
13876
		 * @return bool If tabs were included.
13877
		 */
13878
		function _add_tabs_before_content() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13879
			$this->_logger->entrance();
13880
13881
			if ( ! $this->should_page_include_tabs() ) {
13882
				return false;
13883
			}
13884
13885
			/**
13886
			 * Enqueue the original stylesheets that are included in the
13887
			 * theme settings page. That way, if the theme settings has
13888
			 * some custom _styled_ content above the tabs UI, this
13889
			 * will make sure that the styling is preserved.
13890
			 */
13891
			$stylesheets = $this->_cache->get( 'tabs_stylesheets', array() );
13892
			if ( is_array( $stylesheets ) ) {
13893
				for ( $i = 0, $len = count( $stylesheets ); $i < $len; $i ++ ) {
13894
					wp_enqueue_style( "fs_{$this->_module_id}_tabs_{$i}", $stylesheets[ $i ] );
13895
				}
13896
			}
13897
13898
			// Cut closing </div> tag.
13899
			echo substr( trim( $this->get_tabs_html() ), 0, - 6 );
13900
13901
			return true;
13902
		}
13903
13904
		/**
13905
		 * Add the tabs closing HTML after the setting's page content.
13906
		 *
13907
		 * @author Vova Feldman (@svovaf)
13908
		 * @since  1.2.2.7
13909
		 *
13910
		 * @return bool If tabs closing HTML was included.
13911
		 */
13912
		function _add_tabs_after_content() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13913
			$this->_logger->entrance();
13914
13915
			if ( ! $this->should_page_include_tabs() ) {
13916
				return false;
13917
			}
13918
13919
			echo '</div>';
13920
13921
			return true;
13922
		}
13923
13924
		#endregion
13925
13926
		/**
13927
		 * Add in-page JavaScript to inject the Freemius tabs into
13928
		 * the module's setting tabs section.
13929
		 *
13930
		 * @author Vova Feldman (@svovaf)
13931
		 * @since  1.2.2.7
13932
		 */
13933
		function _add_freemius_tabs() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13934
			$this->_logger->entrance();
13935
13936
			if ( ! $this->should_page_include_tabs() ) {
13937
				return;
13938
			}
13939
13940
			$params = array( 'id' => $this->_module_id );
13941
			fs_require_once_template( 'tabs.php', $params );
13942
		}
13943
13944
		#endregion
13945
13946
		#--------------------------------------------------------------------------------
13947
		#region Customizer Integration for Themes
13948
		#--------------------------------------------------------------------------------
13949
13950
		/**
13951
		 * @author Vova Feldman (@svovaf)
13952
		 * @since  1.2.2.7
13953
		 *
13954
		 * @param WP_Customize_Manager $customizer
13955
		 */
13956
		function _customizer_register($customizer) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13957
			$this->_logger->entrance();
13958
13959
			if ( $this->is_pricing_page_visible() ) {
13960
				require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-upsell-control.php';
13961
13962
				$customizer->add_section( 'freemius_upsell', array(
13963
					'title'    => '&#9733; ' . $this->get_text( 'view-paid-features' ),
13964
					'priority' => 1,
13965
				) );
13966
				$customizer->add_setting( 'freemius_upsell', array(
13967
					'sanitize_callback' => 'esc_html',
13968
				) );
13969
13970
				$customizer->add_control( new FS_Customizer_Upsell_Control( $customizer, 'freemius_upsell', array(
13971
					'fs'       => $this,
13972
					'section'  => 'freemius_upsell',
13973
					'priority' => 100,
13974
				) ) );
13975
			}
13976
13977
			if ( $this->is_page_visible( 'contact' ) || $this->is_page_visible( 'support' ) ) {
13978
				require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-support-section.php';
13979
13980
				// Main Documentation Link In Customizer Root.
13981
				$customizer->add_section( new FS_Customizer_Support_Section( $customizer, 'freemius_support', array(
13982
					'fs'       => $this,
13983
					'priority' => 1000,
13984
				) ) );
13985
			}
13986
		}
13987
13988
		#endregion
13989
13990
		/**
13991
		 * If the theme has a paid version, add some custom
13992
		 * styling to the theme's premium version (if exists)
13993
		 * to highlight that it's the premium version of the
13994
		 * same theme, making it easier for identification
13995
		 * after the user upgrades and upload it to the site.
13996
		 *
13997
		 * @author Vova Feldman (@svovaf)
13998
		 * @since  1.2.2.7
13999
		 */
14000
		function _style_premium_theme() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
14001
			$this->_logger->entrance();
14002
14003
			if ( ! self::is_themes_page() ) {
14004
				// Only include in the themes page.
14005
				return;
14006
			}
14007
14008
			if ( ! $this->has_paid_plan() ) {
14009
				// Only include if has any paid plans.
14010
				return;
14011
			}
14012
14013
			$params = null;
14014
			fs_require_once_template( '/js/jquery.content-change.php', $params );
14015
14016
			$params = array(
14017
				'slug' => $this->_slug,
14018
				'id'   => $this->_module_id,
14019
			);
14020
14021
			fs_require_template( '/js/style-premium-theme.php', $params );
14022
		}
14023
14024
		#----------------------------------------------------------------------------------
14025
		#region Marketing
14026
		#----------------------------------------------------------------------------------
14027
14028
		/**
14029
		 * Check if current user purchased any other plugins before.
14030
		 *
14031
		 * @author Vova Feldman (@svovaf)
14032
		 * @since  1.0.9
14033
		 *
14034
		 * @return bool
14035
		 */
14036
		function has_purchased_before() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
14037
			// TODO: Implement has_purchased_before() method.
14038
			throw new Exception( 'not implemented' );
14039
		}
14040
14041
		/**
14042
		 * Check if current user classified as an agency.
14043
		 *
14044
		 * @author Vova Feldman (@svovaf)
14045
		 * @since  1.0.9
14046
		 *
14047
		 * @return bool
14048
		 */
14049
		function is_agency() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
14050
			// TODO: Implement is_agency() method.
14051
			throw new Exception( 'not implemented' );
14052
		}
14053
14054
		/**
14055
		 * Check if current user classified as a developer.
14056
		 *
14057
		 * @author Vova Feldman (@svovaf)
14058
		 * @since  1.0.9
14059
		 *
14060
		 * @return bool
14061
		 */
14062
		function is_developer() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
14063
			// TODO: Implement is_developer() method.
14064
			throw new Exception( 'not implemented' );
14065
		}
14066
14067
		/**
14068
		 * Check if current user classified as a business.
14069
		 *
14070
		 * @author Vova Feldman (@svovaf)
14071
		 * @since  1.0.9
14072
		 *
14073
		 * @return bool
14074
		 */
14075
		function is_business() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
14076
			// TODO: Implement is_business() method.
14077
			throw new Exception( 'not implemented' );
14078
		}
14079
14080
		#endregion
14081
	}
14082