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 — feature/attachment-taxonomies ( 05a68d...355e81 )
by Brad
03:29
created

Freemius::_data_migration()   C

Complexity

Conditions 8
Paths 9

Size

Total Lines 42
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 17
nc 9
nop 2
dl 0
loc 42
rs 5.3846
c 0
b 0
f 0
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.2.3', '<' ) &&
559
                version_compare( $sdk_version, '1.2.3', '>=' )
560
            ) {
561
                /**
562
                 * Starting from version 1.2.3, paths are stored as relative paths and not absolute paths; so when
563
                 * upgrading to 1.2.3, make paths relative.
564
                 *
565
                 * @author Leo Fajardo (@leorw)
566
                 */
567
                $this->make_paths_relative();
568
            }
569
570
			if ( version_compare( $sdk_prev_version, '1.1.5', '<' ) &&
571
			     version_compare( $sdk_version, '1.1.5', '>=' )
572
			) {
573
				// On version 1.1.5 merged connectivity and is_on data.
574
				if ( isset( $this->_storage->connectivity_test ) ) {
575
					if ( ! isset( $this->_storage->is_on ) ) {
576
						unset( $this->_storage->connectivity_test );
577
					} else {
578
						$connectivity_data              = $this->_storage->connectivity_test;
579
						$connectivity_data['is_active'] = $this->_storage->is_on['is_active'];
580
						$connectivity_data['timestamp'] = $this->_storage->is_on['timestamp'];
581
582
						// Override.
583
						$this->_storage->connectivity_test = $connectivity_data;
584
585
						// Remove previous structure.
586
						unset( $this->_storage->is_on );
587
					}
588
589
				}
590
			}
591
		}
592
593
        /**
594
         * Makes paths relative.
595
         *
596
         * @author Leo Fajardo
597
         * @since 1.2.3
598
         */
599
        private function make_paths_relative() {
600
            $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
601
602
            if ( isset( $id_slug_type_path_map[ $this->_module_id ]['path'] ) ) {
603
                $id_slug_type_path_map[ $this->_module_id ]['path'] = $this->get_relative_path( $id_slug_type_path_map[ $this->_module_id ]['path'] );
604
605
                self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true );
606
            }
607
608
            if ( isset( $this->_storage->plugin_main_file ) ) {
609
                $plugin_main_file = $this->_storage->plugin_main_file;
610
611
                if ( isset( $plugin_main_file->path ) ) {
612
                    $this->_storage->plugin_main_file->path = $this->get_relative_path( $this->_storage->plugin_main_file->path );
613
                } else if ( isset( $plugin_main_file->prev_path ) ) {
614
                    $this->_storage->plugin_main_file->prev_path = $this->get_relative_path( $this->_storage->plugin_main_file->prev_path );
615
                }
616
            }
617
618
            // Remove invalid path that is still associated with the current slug if there's any.
619
            $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
620
            foreach ( $file_slug_map as $plugin_basename => $slug ) {
0 ignored issues
show
Bug introduced by
The expression $file_slug_map of type integer|double|string|nu...boolean|resource|object 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...
621
                if ( $slug === $this->_slug &&
622
                    $plugin_basename !== $this->_plugin_basename &&
623
                    ! file_exists( $this->get_absolute_path( $plugin_basename ) )
624
                ) {
625
                    unset( $file_slug_map[ $plugin_basename ] );
626
                    self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true );
627
628
                    break;
629
                }
630
            }
631
        }
632
633
		/**
634
		 * @author Vova Feldman (@svovaf)
635
		 * @since  1.2.2.7
636
		 *
637
		 * @param string $plugin_prev_version
638
		 * @param string $plugin_version
639
		 */
640
		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...
641
			if ( $this->is_theme() ) {
642
				// Expire the cache of the previous tabs since the theme may
643
				// have setting updates.
644
				$this->_cache->expire( 'tabs' );
645
				$this->_cache->expire( 'tabs_stylesheets' );
646
			}
647
		}
648
649
		/**
650
		 * This action is connected to the 'plugins_loaded' hook and helps to determine
651
		 * if this is a new plugin installation or a plugin update.
652
		 *
653
		 * There are 3 different use-cases:
654
		 *    1) New plugin installation right with Freemius:
655
		 *       1.1 _activate_plugin_event_hook() will be executed first
656
		 *       1.2 Since $this->_storage->is_plugin_new_install is not set,
657
		 *           and $this->_storage->plugin_last_version is not set,
658
		 *           $this->_storage->is_plugin_new_install will be set to TRUE.
659
		 *       1.3 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
660
		 *           be already set to TRUE.
661
		 *
662
		 *    2) Plugin update, didn't have Freemius before, and now have the SDK:
663
		 *       2.1 _activate_plugin_event_hook() will not be executed, because
664
		 *           the activation hook do NOT fires on updates since WP 3.1.
665
		 *       2.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
666
		 *           be empty, therefore, it will be set to FALSE.
667
		 *
668
		 *    3) Plugin update, had Freemius in prev version as well:
669
		 *       3.1 _version_updates_handler() will be executed 1st, since FS was installed
670
		 *           before, $this->_storage->plugin_last_version will NOT be empty,
671
		 *           therefore, $this->_storage->is_plugin_new_install will be set to FALSE.
672
		 *       3.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install is
673
		 *           already set, therefore, it will not be modified.
674
		 *
675
		 *    Use-case #3 is backward compatible, #3.1 will be executed since 1.0.9.
676
		 *
677
		 * NOTE:
678
		 *    The only fallback of this mechanism is if an admin updates a plugin based on use-case #2,
679
		 *    and then, the next immediate PageView is the plugin's main settings page, it will not
680
		 *    show the opt-in right away. The reason it will happen is because Freemius execution
681
		 *    will be turned off till the plugin is fully loaded at least once
682
		 *    (till $this->_storage->was_plugin_loaded is TRUE).
683
		 *
684
		 * @author Vova Feldman (@svovaf)
685
		 * @since  1.1.9
686
		 *
687
		 */
688
		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...
689
			// Update flag that plugin was loaded with Freemius at least once.
690
			$this->_storage->was_plugin_loaded = true;
691
692
			/**
693
			 * Bug fix - only set to false when it's a plugin, due to the
694
			 * execution sequence of the theme hooks and our methods, if
695
			 * this will be set for themes, Freemius will always assume
696
			 * it's a theme update.
697
			 *
698
			 * @author Vova Feldman (@svovaf)
699
			 * @since  1.2.2.2
700
			 */
701
			if ( $this->is_plugin() &&
702
			     ! isset( $this->_storage->is_plugin_new_install )
703
			) {
704
				$this->_storage->is_plugin_new_install = false;
705
			}
706
		}
707
708
		/**
709
		 * @author Vova Feldman (@svovaf)
710
		 * @since  1.0.9
711
		 */
712
		private function _register_hooks() {
713
			$this->_logger->entrance();
714
715
			if ( is_admin() ) {
716
				if ( $this->is_plugin() ) {
717
					$plugin_dir = dirname( $this->_plugin_dir_path ) . '/';
718
719
					/**
720
					 * @since 1.2.2
721
					 *
722
					 * Hook to both free and premium version activations to support
723
					 * auto deactivation on the other version activation.
724
					 */
725
					register_activation_hook(
726
						$plugin_dir . $this->_free_plugin_basename,
727
						array( &$this, '_activate_plugin_event_hook' )
728
					);
729
730
					register_activation_hook(
731
						$plugin_dir . $this->premium_plugin_basename(),
732
						array( &$this, '_activate_plugin_event_hook' )
733
					);
734
				} else {
735
					add_action( 'after_switch_theme', array( &$this, '_activate_theme_event_hook' ), 10, 2 );
736
737
					/**
738
					 * Include the required hooks to capture the theme settings' page tabs
739
					 * and cache them.
740
					 *
741
					 * @author Vova Feldman (@svovaf)
742
					 * @since 1.2.2.7
743
					 */
744
					if ( ! $this->_cache->has_valid( 'tabs' ) ) {
745
						add_action( 'admin_footer', array( &$this, '_tabs_capture' ) );
746
						// Add license activation AJAX callback.
747
						$this->add_ajax_action( 'store_tabs', array( &$this, '_store_tabs_ajax_action' ) );
748
749
						add_action( 'admin_enqueue_scripts', array( &$this, '_store_tabs_styles' ), 9999999 );
750
					}
751
752
					add_action(
753
						'admin_footer',
754
						array( &$this, '_add_freemius_tabs' ),
755
						/**
756
						 * The tabs JS code must be executed after the tabs capture logic (_tabs_capture()).
757
						 * That's why the priority is 11 while the tabs capture logic is added
758
						 * with priority 10.
759
						 *
760
						 * @author Vova Feldman (@svovaf)
761
						 */
762
						11
763
					);
764
765
					add_action( 'admin_footer', array( &$this, '_style_premium_theme' ) );
766
				}
767
768
				/**
769
				 * Part of the mechanism to identify new plugin install vs. plugin update.
770
				 *
771
				 * @author Vova Feldman (@svovaf)
772
				 * @since  1.1.9
773
				 */
774
				if ( empty( $this->_storage->was_plugin_loaded ) ) {
775
					if ( $this->is_plugin() &&
776
					     $this->is_activation_mode( false ) &&
777
					     0 == did_action( 'plugins_loaded' )
778
					) {
779
						add_action( 'plugins_loaded', array( &$this, '_plugins_loaded' ) );
780
					} else {
781
						// If was activated before, then it was already loaded before.
782
						$this->_plugins_loaded();
783
					}
784
				}
785
786
				if ( ! self::is_ajax() ) {
787
					if ( ! $this->is_addon() ) {
788
						add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY );
789
					}
790
				}
791
			}
792
793
			if ( $this->is_plugin() ) {
794
				register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) );
795
			}
796
797
			if ( $this->is_theme() && self::is_customizer() ) {
798
				// Register customizer upsell.
799
				add_action( 'customize_register', array( &$this, '_customizer_register' ) );
800
			}
801
802
			add_action( 'init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY );
803
804
			add_action( 'admin_init', array( &$this, '_add_tracking_links' ) );
805
			add_action( 'admin_init', array( &$this, '_add_license_activation' ) );
806
			$this->add_ajax_action( 'update_billing', array( &$this, '_update_billing_ajax_action' ) );
807
			$this->add_ajax_action( 'start_trial', array( &$this, '_start_trial_ajax_action' ) );
808
809
			$this->add_ajax_action( 'install_premium_version', array(
810
				&$this,
811
				'_install_premium_version_ajax_action'
812
			) );
813
814
            $this->add_ajax_action( 'submit_affiliate_application', array( &$this, '_submit_affiliate_application' ) );
815
816
			$this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) );
817
818
			$this->add_action( 'sdk_version_update', array( &$this, '_data_migration' ), WP_FS__DEFAULT_PRIORITY, 2 );
819
			$this->add_action( 'plugin_version_update', array( &$this, '_after_version_update' ), WP_FS__DEFAULT_PRIORITY, 2 );
820
			$this->add_filter( 'after_code_type_change', array( &$this, '_after_code_type_change' ) );
821
822
			add_action( 'admin_init', array( &$this, '_add_trial_notice' ) );
823
			add_action( 'admin_init', array( &$this, '_add_affiliate_program_notice' ) );
824
			add_action( 'admin_init', array( &$this, '_enqueue_common_css' ) );
825
826
			/**
827
			 * Handle request to reset anonymous mode for `get_reconnect_url()`.
828
			 *
829
			 * @author Vova Feldman (@svovaf)
830
			 * @since  1.2.1.5
831
			 */
832
			if ( fs_request_is_action( 'reset_anonymous_mode' ) &&
833
			     $this->get_unique_affix() === fs_request_get( 'fs_unique_affix' )
834
			) {
835
				add_action( 'admin_init', array( &$this, 'connect_again' ) );
836
			}
837
		}
838
839
		/**
840
		 * Keeping the uninstall hook registered for free or premium plugin version may result to a fatal error that
841
		 * could happen when a user tries to uninstall either version while one of them is still active. Uninstalling a
842
		 * plugin will trigger inclusion of the free or premium version and if one of them is active during the
843
		 * uninstallation, a fatal error may occur in case the plugin's class or functions are already defined.
844
		 *
845
		 * @author Leo Fajardo (leorw)
846
		 *
847
		 * @since  1.2.0
848
		 */
849
		private function unregister_uninstall_hook() {
850
			$uninstallable_plugins = (array) get_option( 'uninstall_plugins' );
851
			unset( $uninstallable_plugins[ $this->_free_plugin_basename ] );
852
			unset( $uninstallable_plugins[ $this->premium_plugin_basename() ] );
853
854
			update_option( 'uninstall_plugins', $uninstallable_plugins );
855
		}
856
857
		/**
858
		 * @since 1.2.0 Invalidate module's main file cache, otherwise, FS_Plugin_Updater will not fetch updates.
859
		 */
860
		private function clear_module_main_file_cache() {
861
			if ( ! isset( $this->_storage->plugin_main_file ) ||
862
			     empty( $this->_storage->plugin_main_file->path )
863
			) {
864
				return;
865
			}
866
867
			$plugin_main_file = clone $this->_storage->plugin_main_file;
868
869
			// Store cached path (2nd layer cache).
870
			$plugin_main_file->prev_path = $plugin_main_file->path;
871
872
			// Clear cached path.
873
			unset( $plugin_main_file->path );
874
875
			$this->_storage->plugin_main_file = $plugin_main_file;
876
877
			/**
878
			 * Clear global cached path.
879
			 *
880
			 * @author Leo Fajardo (@leorw)
881
			 * @since  1.2.2
882
			 */
883
			$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map' );
884
			unset( $id_slug_type_path_map[ $this->_module_id ]['path'] );
885
			self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true );
886
		}
887
888
		/**
889
		 * @author Vova Feldman (@svovaf)
890
		 * @since  1.0.9
891
		 */
892
		private function _register_account_hooks() {
893
			if ( ! is_admin() ) {
894
				return;
895
			}
896
897
			/**
898
			 * Always show the deactivation feedback form since we added
899
			 * automatic free version deactivation upon premium code activation.
900
			 *
901
			 * @since 1.2.1.6
902
			 */
903
			$this->add_ajax_action(
904
				'submit_uninstall_reason',
905
				array( &$this, '_submit_uninstall_reason_action' )
906
			);
907
908
			if ( ( $this->is_plugin() && self::is_plugins_page() ) ||
909
			     ( $this->is_theme() && self::is_themes_page() )
910
			) {
911
				add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) );
912
			}
913
		}
914
915
		/**
916
		 * Leverage backtrace to find caller plugin file path.
917
		 *
918
		 * @author Vova Feldman (@svovaf)
919
		 * @since  1.0.6
920
		 *
921
		 * @param  bool $is_init Is initiation sequence.
922
		 *
923
		 * @return string
924
		 */
925
		private function _find_caller_plugin_file( $is_init = false ) {
926
			// Try to load the cached value of the file path.
927
			if ( isset( $this->_storage->plugin_main_file ) ) {
928
				$plugin_main_file = $this->_storage->plugin_main_file;
929
                if ( isset( $plugin_main_file->path ) ) {
930
                    $absolute_path = $this->get_absolute_path( $plugin_main_file->path );
931
                    if ( file_exists( $absolute_path ) ) {
932
                        return $absolute_path;
933
                    }
934
				}
935
			}
936
937
			/**
938
			 * @since 1.2.1
939
			 *
940
			 * `clear_module_main_file_cache()` is clearing the plugin's cached path on
941
			 * deactivation. Therefore, if any plugin/theme was initiating `Freemius`
942
			 * with that plugin's slug, it was overriding the empty plugin path with a wrong path.
943
			 *
944
			 * So, we've added a special mechanism with a 2nd layer of cache that uses `prev_path`
945
			 * when the class instantiator isn't the module.
946
			 */
947
			if ( ! $is_init ) {
948
				// Fetch prev path cache.
949
				if ( isset( $this->_storage->plugin_main_file ) &&
950
				     isset( $this->_storage->plugin_main_file->prev_path )
951
				) {
952
                    $absolute_path = $this->get_absolute_path( $this->_storage->plugin_main_file->prev_path );
953
					if ( file_exists( $absolute_path ) ) {
954
						return $absolute_path;
955
					}
956
				}
957
958
				wp_die(
959
					$this->get_text_inline( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact [email protected] with the current error.', 'failed-finding-main-path' ) .
960
					" Module: {$this->_slug}; SDK: " . WP_FS__SDK_VERSION . ";",
961
					$this->get_text_inline( 'Error', 'error' ),
962
					array( 'back_link' => true )
963
				);
964
			}
965
966
			/**
967
			 * @since 1.2.1
968
			 *
969
			 * Only the original instantiator that calls dynamic_init can modify the module's path.
970
			 */
971
			// Find caller module.
972
			$id_slug_type_path_map            = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
973
			$this->_storage->plugin_main_file = (object) array(
974
				'path' => $id_slug_type_path_map[ $this->_module_id ]['path'],
975
			);
976
977
			return $this->get_absolute_path( $id_slug_type_path_map[ $this->_module_id ]['path'] );
978
		}
979
980
        /**
981
         * @author Leo Fajardo (@leorw)
982
         * @since 1.2.3
983
         *
984
         * @param string $path
985
         *
986
         * @return string
987
         */
988
        private function get_relative_path( $path ) {
989
            $module_root_dir = $this->get_module_root_dir_path();
990
            if ( 0 === strpos( $path, $module_root_dir ) ) {
991
                $path = substr( $path, strlen( $module_root_dir ) );
992
            }
993
994
            return $path;
995
        }
996
997
        /**
998
         * @author Leo Fajardo (@leorw)
999
         * @since 1.2.3
1000
         *
1001
         * @param string      $path
1002
         * @param string|bool $module_type
1003
         *
1004
         * @return string
1005
         */
1006
        private function get_absolute_path( $path, $module_type = false ) {
1007
            $module_root_dir = $this->get_module_root_dir_path( $module_type );
1008
            if ( 0 !== strpos( $path, $module_root_dir ) ) {
1009
                $path = fs_normalize_path( $module_root_dir . $path );
1010
            }
1011
1012
            return $path;
1013
        }
1014
1015
        /**
1016
         * @author Leo Fajardo (@leorw)
1017
         * @since 1.2.3
1018
         *
1019
         * @param string|bool $module_type
1020
         *
1021
         * @return string
1022
         */
1023
        private function get_module_root_dir_path( $module_type = false ) {
1024
            $is_plugin = empty( $module_type ) ?
1025
                $this->is_plugin() :
1026
                ( WP_FS__MODULE_TYPE_PLUGIN === $module_type );
1027
1028
            return fs_normalize_path( trailingslashit( $is_plugin ?
1029
                WP_PLUGIN_DIR :
1030
                get_theme_root() ) );
1031
        }
1032
1033
		/**
1034
		 * @author Leo Fajardo (@leorw)
1035
		 *
1036
		 * @param number $module_id
1037
		 * @param string $slug
1038
		 *
1039
		 * @since  1.2.2
1040
		 */
1041
		private function store_id_slug_type_path_map( $module_id, $slug ) {
1042
			$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
1043
1044
			$store_option = false;
1045
1046
			if ( ! isset( $id_slug_type_path_map[ $module_id ] ) ) {
1047
				$id_slug_type_path_map[ $module_id ] = array(
1048
					'slug' => $slug
1049
				);
1050
1051
				$store_option = true;
1052
			}
1053
1054
			if ( ! isset( $id_slug_type_path_map[ $module_id ]['path'] ) ||
1055
			     /**
1056
			      * This verification is for cases when suddenly the same module
1057
			      * is installed but with a different folder name.
1058
			      *
1059
			      * @author Vova Feldman (@svovaf)
1060
			      * @since 1.2.3
1061
			      */
1062
                ! file_exists( $this->get_absolute_path(
1063
                    $id_slug_type_path_map[ $module_id ]['path'],
1064
                    $id_slug_type_path_map[ $module_id ]['type']
1065
                ) )
1066
			) {
1067
				$caller_main_file_and_type = $this->get_caller_main_file_and_type();
1068
1069
				$id_slug_type_path_map[ $module_id ]['type'] = $caller_main_file_and_type->module_type;
1070
				$id_slug_type_path_map[ $module_id ]['path'] = $caller_main_file_and_type->path;
1071
1072
				$store_option = true;
1073
			}
1074
1075
			if ( $store_option ) {
1076
				self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true );
1077
			}
1078
		}
1079
1080
		/**
1081
		 * Identifies the caller type: plugin or theme.
1082
		 *
1083
		 * @author Leo Fajardo (@leorw)
1084
		 * @since  1.2.2
1085
		 *
1086
		 * @author Vova Feldman (@svovaf)
1087
		 * @since  1.2.2.3 Find the earliest module in the call stack that calls to the SDK. This fix is for cases when
1088
		 *         add-ons are relying on loading the SDK from the parent module, and also allows themes including the
1089
		 *         SDK an internal file instead of directly from functions.php.
1090
		 * @since  1.2.1.7 Knows how to handle cases when an add-on includes the parent module logic.
1091
		 */
1092
		private function get_caller_main_file_and_type() {
1093
			self::require_plugin_essentials();
1094
1095
			$all_plugins       = get_plugins();
1096
			$all_plugins_paths = array();
1097
1098
			// Get active plugin's main files real full names (might be symlinks).
1099
			foreach ( $all_plugins as $relative_path => &$data ) {
1100
				if ( false === strpos( fs_normalize_path( $relative_path ), '/' ) ) {
1101
					/**
1102
					 * Ignore plugins that don't have a folder (e.g. Hello Dolly) since they
1103
					 * can't really include the SDK.
1104
					 *
1105
					 * @author Vova Feldman
1106
					 * @since  1.2.1.7
1107
					 */
1108
					continue;
1109
				}
1110
1111
				$all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) );
1112
			}
1113
1114
			$caller_file_candidate = false;
1115
			$caller_map            = array();
1116
			$module_type           = WP_FS__MODULE_TYPE_PLUGIN;
1117
			$themes_dir            = fs_normalize_path( get_theme_root() );
1118
1119
			for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) {
1120
				if ( empty( $bt[ $i ]['file'] ) ) {
1121
					continue;
1122
				}
1123
1124
				if ( $i > 1 && ! empty( $bt[ $i - 1 ]['file'] ) && $bt[ $i ]['file'] === $bt[ $i - 1 ]['file'] ) {
1125
					// If file same as the prev file in the stack, skip it.
1126
					continue;
1127
				}
1128
1129
				if ( ! empty( $bt[ $i ]['function'] ) && in_array( $bt[ $i ]['function'], array(
1130
						'do_action',
1131
						'apply_filter',
1132
						// The string split is stupid, but otherwise, theme check
1133
						// throws info notices.
1134
						'requir' . 'e_once',
1135
						'requir' . 'e',
1136
						'includ' . 'e_once',
1137
						'includ' . 'e'
1138
					) )
1139
				) {
1140
					// Ignore call stack hooks and files inclusion.
1141
					continue;
1142
				}
1143
1144
				$caller_file_path = fs_normalize_path( $bt[ $i ]['file'] );
1145
1146
				if ( 'functions.php' === basename( $caller_file_path ) ) {
1147
					/**
1148
					 * 1. Assumes that theme's starting execution file is functions.php.
1149
					 * 2. This complex logic fixes symlink issues (e.g. with Vargant).
1150
					 *
1151
					 * @author Vova Feldman (@svovaf)
1152
					 * @since  1.2.2.5
1153
					 */
1154
1155
					if ( $caller_file_path == fs_normalize_path( realpath( trailingslashit( $themes_dir ) . basename( dirname( $caller_file_path ) ) . '/' . basename( $caller_file_path ) ) ) ) {
1156
						$module_type = WP_FS__MODULE_TYPE_THEME;
1157
1158
                        /**
1159
                         * Relative path of the theme, e.g.:
1160
                         * `my-theme/functions.php`
1161
                         *
1162
                         * @author Leo Fajardo (@leorw)
1163
                         */
1164
                        $caller_file_candidate = basename( dirname( $caller_file_path ) ) .
1165
                            '/' .
1166
                            basename( $caller_file_path );
1167
1168
						continue;
1169
					}
1170
				}
1171
1172
				$caller_file_hash = md5( $caller_file_path );
1173
1174
				if ( ! isset( $caller_map[ $caller_file_hash ] ) ) {
1175
					foreach ( $all_plugins_paths as $plugin_path ) {
1176
						if ( false !== strpos( $caller_file_path, fs_normalize_path( dirname( $plugin_path ) . '/' ) ) ) {
1177
							$caller_map[ $caller_file_hash ] = fs_normalize_path( $plugin_path );
1178
							break;
1179
						}
1180
					}
1181
				}
1182
1183
				if ( isset( $caller_map[ $caller_file_hash ] ) ) {
1184
					$module_type           = WP_FS__MODULE_TYPE_PLUGIN;
1185
					$caller_file_candidate = plugin_basename( $caller_map[ $caller_file_hash ] );
1186
				}
1187
			}
1188
1189
			return (object) array(
1190
				'module_type' => $module_type,
1191
				'path'        => $caller_file_candidate
1192
			);
1193
		}
1194
1195
		#----------------------------------------------------------------------------------
1196
		#region Deactivation Feedback Form
1197
		#----------------------------------------------------------------------------------
1198
1199
		/**
1200
		 * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins
1201
		 * page.
1202
		 *
1203
		 * @author Vova Feldman (@svovaf)
1204
		 * @author Leo Fajardo (@leorw)
1205
		 * @since  1.1.2
1206
		 */
1207
		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...
1208
			/* Check the type of user:
1209
			 * 1. Long-term (long-term)
1210
			 * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term).
1211
			 * 3. Short-term (short-term)
1212
			 */
1213
			$is_long_term_user = true;
1214
1215
			// Check if the site is at least 2 days old.
1216
			$time_installed = $this->_storage->install_timestamp;
1217
1218
			// Difference in seconds.
1219
			$date_diff = time() - $time_installed;
1220
1221
			// Convert seconds to days.
1222
			$date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) );
1223
1224
			if ( $date_diff_days < 2 ) {
1225
				$is_long_term_user = false;
1226
			}
1227
1228
			$is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user );
1229
1230
			if ( $is_long_term_user ) {
1231
				$user_type = 'long-term';
1232
			} else {
1233
				if ( ! $this->is_registered() && ! $this->is_anonymous() ) {
1234
					$user_type = 'non-registered-and-non-anonymous-short-term';
1235
				} else {
1236
					$user_type = 'short-term';
1237
				}
1238
			}
1239
1240
			$uninstall_reasons = $this->_get_uninstall_reasons( $user_type );
1241
1242
			// Load the HTML template for the deactivation feedback dialog box.
1243
			$vars = array(
1244
				'reasons' => $uninstall_reasons,
1245
				'id'      => $this->_module_id
1246
			);
1247
1248
			/**
1249
			 * @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.
1250
			 */
1251
			fs_require_template( 'forms/deactivation/form.php', $vars );
1252
		}
1253
1254
		/**
1255
		 * @author Leo Fajardo (leorw)
1256
		 * @since  1.1.2
1257
		 *
1258
		 * @param string $user_type
1259
		 *
1260
		 * @return array The uninstall reasons for the specified user type.
1261
		 */
1262
		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...
1263
			$module_type = $this->_module_type;
1264
1265
			$internal_message_template_var = array(
1266
				'id' => $this->_module_id
1267
			);
1268
1269
			if ( $this->is_registered() && false !== $this->get_plan() && $this->get_plan()->has_technical_support() ) {
1270
				$contact_support_template = fs_get_template( 'forms/deactivation/contact.php', $internal_message_template_var );
1271
			} else {
1272
				$contact_support_template = '';
1273
			}
1274
1275
			$reason_found_better_plugin = array(
1276
				'id'                => self::REASON_FOUND_A_BETTER_PLUGIN,
1277
				'text'              => sprintf( $this->get_text_inline( 'I found a better %s', 'reason-found-a-better-plugin' ), $module_type ),
1278
				'input_type'        => 'textfield',
1279
				'input_placeholder' => sprintf( $this->get_text_inline( "What's the %s's name?", 'placeholder-plugin-name' ), $module_type ),
1280
			);
1281
1282
			$reason_temporary_deactivation = array(
1283
				'id'                => self::REASON_TEMPORARY_DEACTIVATION,
1284
				'text'              => sprintf(
1285
					$this->get_text_inline( "It's a temporary %s. I'm just debugging an issue.", 'reason-temporary-x' ),
1286
					strtolower( $this->is_plugin() ?
1287
						$this->get_text_inline( 'Deactivation', 'deactivation' ) :
1288
						$this->get_text_inline( 'Theme Switch', 'theme-switch' )
1289
					)
1290
				),
1291
				'input_type'        => '',
1292
				'input_placeholder' => ''
1293
			);
1294
1295
			$reason_other = array(
1296
				'id'                => self::REASON_OTHER,
1297
				'text'              => $this->get_text_inline( 'Other', 'reason-other' ),
1298
				'input_type'        => 'textfield',
1299
				'input_placeholder' => ''
1300
			);
1301
1302
			$long_term_user_reasons = array(
1303
				array(
1304
					'id'                => self::REASON_NO_LONGER_NEEDED,
1305
					'text'              => sprintf( $this->get_text_inline( 'I no longer need the %s', 'reason-no-longer-needed' ), $module_type ),
1306
					'input_type'        => '',
1307
					'input_placeholder' => ''
1308
				),
1309
				$reason_found_better_plugin,
1310
				array(
1311
					'id'                => self::REASON_NEEDED_FOR_A_SHORT_PERIOD,
1312
					'text'              => sprintf( $this->get_text_inline( 'I only needed the %s for a short period', 'reason-needed-for-a-short-period' ), $module_type ),
1313
					'input_type'        => '',
1314
					'input_placeholder' => ''
1315
				),
1316
				array(
1317
					'id'                => self::REASON_BROKE_MY_SITE,
1318
					'text'              => sprintf( $this->get_text_inline( 'The %s broke my site', 'reason-broke-my-site' ), $module_type ),
1319
					'input_type'        => '',
1320
					'input_placeholder' => '',
1321
					'internal_message'  => $contact_support_template
1322
				),
1323
				array(
1324
					'id'                => self::REASON_SUDDENLY_STOPPED_WORKING,
1325
					'text'              => sprintf( $this->get_text_inline( 'The %s suddenly stopped working', 'reason-suddenly-stopped-working' ), $module_type ),
1326
					'input_type'        => '',
1327
					'input_placeholder' => '',
1328
					'internal_message'  => $contact_support_template
1329
				)
1330
			);
1331
1332
			if ( $this->is_paying() ) {
1333
				$long_term_user_reasons[] = array(
1334
					'id'                => self::REASON_CANT_PAY_ANYMORE,
1335
					'text'              => $this->get_text_inline( "I can't pay for it anymore", 'reason-cant-pay-anymore' ),
1336
					'input_type'        => 'textfield',
1337
					'input_placeholder' => $this->get_text_inline( 'What price would you feel comfortable paying?', 'placeholder-comfortable-price' )
1338
				);
1339
			}
1340
1341
			$reason_dont_share_info = array(
1342
				'id'                => self::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION,
1343
				'text'              => $this->get_text_inline( "I don't like to share my information with you", 'reason-dont-like-to-share-my-information' ),
1344
				'input_type'        => '',
1345
				'input_placeholder' => ''
1346
			);
1347
1348
			/**
1349
			 * If the current user has selected the "don't share data" reason in the deactivation feedback modal, inform the
1350
			 * user by showing additional message that he doesn't have to share data and can just choose to skip the opt-in
1351
			 * (the Skip button is included in the message to show). This message will only be shown if anonymous mode is
1352
			 * enabled and the user's account is currently not in pending activation state (similar to the way the Skip
1353
			 * button in the opt-in form is shown/hidden).
1354
			 */
1355
			if ( $this->is_enable_anonymous() && ! $this->is_pending_activation() ) {
1356
				$reason_dont_share_info['internal_message'] = fs_get_template( 'forms/deactivation/retry-skip.php', $internal_message_template_var );
1357
			}
1358
1359
			$uninstall_reasons = array(
1360
				'long-term'                                   => $long_term_user_reasons,
1361
				'non-registered-and-non-anonymous-short-term' => array(
1362
					array(
1363
						'id'                => self::REASON_DIDNT_WORK,
1364
						'text'              => sprintf( $this->get_text_inline( "The %s didn't work", 'reason-didnt-work' ), $module_type ),
1365
						'input_type'        => '',
1366
						'input_placeholder' => ''
1367
					),
1368
					$reason_dont_share_info,
1369
					$reason_found_better_plugin
1370
				),
1371
				'short-term'                                  => array(
1372
					array(
1373
						'id'                => self::REASON_COULDNT_MAKE_IT_WORK,
1374
						'text'              => $this->get_text_inline( "I couldn't understand how to make it work", 'reason-couldnt-make-it-work' ),
1375
						'input_type'        => '',
1376
						'input_placeholder' => '',
1377
						'internal_message'  => $contact_support_template
1378
					),
1379
					$reason_found_better_plugin,
1380
					array(
1381
						'id'                => self::REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE,
1382
						'text'              => sprintf( $this->get_text_inline( "The %s is great, but I need specific feature that you don't support", 'reason-great-but-need-specific-feature' ), $module_type ),
1383
						'input_type'        => 'textarea',
1384
						'input_placeholder' => $this->get_text_inline( 'What feature?', 'placeholder-feature' )
1385
					),
1386
					array(
1387
						'id'                => self::REASON_NOT_WORKING,
1388
						'text'              => sprintf( $this->get_text_inline( 'The %s is not working', 'reason-not-working' ), $module_type ),
1389
						'input_type'        => 'textarea',
1390
						'input_placeholder' => $this->get_text_inline( "Kindly share what didn't work so we can fix it for future users...", 'placeholder-share-what-didnt-work' )
1391
					),
1392
					array(
1393
						'id'                => self::REASON_NOT_WHAT_I_WAS_LOOKING_FOR,
1394
						'text'              => $this->get_text_inline( "It's not what I was looking for", 'reason-not-what-i-was-looking-for' ),
1395
						'input_type'        => 'textarea',
1396
						'input_placeholder' => $this->get_text_inline( "What you've been looking for?", 'placeholder-what-youve-been-looking-for' )
1397
					),
1398
					array(
1399
						'id'                => self::REASON_DIDNT_WORK_AS_EXPECTED,
1400
						'text'              => sprintf( $this->get_text_inline( "The %s didn't work as expected", 'reason-didnt-work-as-expected' ), $module_type ),
1401
						'input_type'        => 'textarea',
1402
						'input_placeholder' => $this->get_text_inline( 'What did you expect?', 'placeholder-what-did-you-expect' )
1403
					)
1404
				)
1405
			);
1406
1407
			// Randomize the reasons for the current user type.
1408
			shuffle( $uninstall_reasons[ $user_type ] );
1409
1410
			// Keep the following reasons as the last items in the list.
1411
			$uninstall_reasons[ $user_type ][] = $reason_temporary_deactivation;
1412
			$uninstall_reasons[ $user_type ][] = $reason_other;
1413
1414
			$uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons );
1415
1416
			return $uninstall_reasons[ $user_type ];
1417
		}
1418
1419
		/**
1420
		 * Called after the user has submitted his reason for deactivating the plugin.
1421
		 *
1422
		 * @author Leo Fajardo (@leorw)
1423
		 * @since  1.1.2
1424
		 */
1425
		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...
1426
			$this->_logger->entrance();
1427
1428
			$this->check_ajax_referer( 'submit_uninstall_reason' );
1429
1430
			$reason_id = fs_request_get( 'reason_id' );
1431
1432
			// Check if the given reason ID is an unsigned integer.
1433
			if ( ! ctype_digit( $reason_id ) ) {
1434
				exit;
1435
			}
1436
1437
			$reason_info = trim( fs_request_get( 'reason_info', '' ) );
1438
			if ( ! empty( $reason_info ) ) {
1439
				$reason_info = substr( $reason_info, 0, 128 );
1440
			}
1441
1442
			$reason = (object) array(
1443
				'id'           => $reason_id,
1444
				'info'         => $reason_info,
1445
				'is_anonymous' => fs_request_get_bool( 'is_anonymous' )
1446
			);
1447
1448
			$this->_storage->store( 'uninstall_reason', $reason );
1449
1450
			/**
1451
			 * If the module type is "theme", trigger the uninstall event here (on theme deactivation) since themes do
1452
			 * not support uninstall hook.
1453
			 *
1454
			 * @author Leo Fajardo (@leorw)
1455
			 * @since  1.2.2
1456
			 */
1457
			if ( $this->is_theme() ) {
1458
				$this->_uninstall_plugin_event( false );
1459
				$this->remove_sdk_reference();
1460
			}
1461
1462
			// Print '1' for successful operation.
1463
			echo 1;
1464
			exit;
1465
		}
1466
1467
		#endregion
1468
1469
		#----------------------------------------------------------------------------------
1470
		#region Instance
1471
		#----------------------------------------------------------------------------------
1472
1473
		/**
1474
		 * Main singleton instance.
1475
		 *
1476
		 * @author Vova Feldman (@svovaf)
1477
		 * @since  1.0.0
1478
		 *
1479
		 * @param  number      $module_id
1480
		 * @param  string|bool $slug
1481
		 * @param  bool        $is_init Is initiation sequence.
1482
		 *
1483
		 * @return Freemius|false
1484
		 */
1485
		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...
1486
			if ( empty( $module_id ) ) {
1487
				return false;
1488
			}
1489
1490
			if ( ! is_numeric( $module_id ) ) {
1491
				if ( ! $is_init && true === $slug ) {
1492
					$is_init = true;
1493
				}
1494
1495
				$slug = $module_id;
1496
1497
				$module = FS_Plugin_Manager::instance( $slug )->get();
1498
1499
				if ( is_object( $module ) ) {
1500
					$module_id = $module->id;
1501
				}
1502
			}
1503
1504
			$key = 'm_' . $module_id;
1505
1506
			if ( ! isset( self::$_instances[ $key ] ) ) {
1507
				if ( 0 === count( self::$_instances ) ) {
1508
					self::_load_required_static();
1509
				}
1510
1511
				self::$_instances[ $key ] = new Freemius( $module_id, $slug, $is_init );
1512
			}
1513
1514
			return self::$_instances[ $key ];
1515
		}
1516
1517
		/**
1518
		 * @author Vova Feldman (@svovaf)
1519
		 * @since  1.0.6
1520
		 *
1521
		 * @param number $addon_id
1522
		 *
1523
		 * @return bool
1524
		 */
1525
		private static function has_instance( $addon_id ) {
1526
			return isset( self::$_instances[ 'm_' . $addon_id ] );
1527
		}
1528
1529
		/**
1530
		 * @author Leo Fajardo (@leorw)
1531
		 * @since  1.2.2
1532
		 *
1533
		 * @param  string|number $id_or_slug
1534
		 *
1535
		 * @return number|false
1536
		 */
1537
		private static function get_module_id( $id_or_slug ) {
1538
			if ( is_numeric( $id_or_slug ) ) {
1539
				return $id_or_slug;
1540
			}
1541
1542
			foreach ( self::$_instances as $instance ) {
1543
				if ( $instance->is_plugin() && ( $id_or_slug === $instance->get_slug() ) ) {
1544
					return $instance->get_id();
1545
				}
1546
			}
1547
1548
			return false;
1549
		}
1550
1551
		/**
1552
		 * @author Vova Feldman (@svovaf)
1553
		 * @since  1.0.6
1554
		 *
1555
		 * @param number $id
1556
		 *
1557
		 * @return false|Freemius
1558
		 */
1559
		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...
1560
			return isset ( self::$_instances[ 'm_' . $id ] ) ?
1561
				self::$_instances[ 'm_' . $id ] :
1562
				false;
1563
		}
1564
1565
		/**
1566
		 *
1567
		 * @author Vova Feldman (@svovaf)
1568
		 * @since  1.0.1
1569
		 *
1570
		 * @param $plugin_file
1571
		 *
1572
		 * @return false|Freemius
1573
		 */
1574
		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...
1575
			$slug = self::find_slug_by_basename( $plugin_file );
1576
1577
			return ( false !== $slug ) ?
1578
				self::instance( self::get_module_id( $slug ) ) :
1579
				false;
1580
		}
1581
1582
		/**
1583
		 * @author Vova Feldman (@svovaf)
1584
		 * @since  1.0.6
1585
		 *
1586
		 * @return false|Freemius
1587
		 */
1588
		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...
1589
			return self::get_instance_by_id( $this->_plugin->parent_plugin_id );
1590
		}
1591
1592
		/**
1593
		 * @author Vova Feldman (@svovaf)
1594
		 * @since  1.0.6
1595
		 *
1596
		 * @param  string|number $id_or_slug
1597
		 *
1598
		 * @return false|Freemius
1599
		 */
1600
		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...
1601
			$addon_id = self::get_module_id( $id_or_slug );
1602
1603
			return self::instance( $addon_id );
1604
		}
1605
1606
		#endregion ------------------------------------------------------------------
1607
1608
		/**
1609
		 * @author Vova Feldman (@svovaf)
1610
		 * @since  1.0.6
1611
		 *
1612
		 * @return bool
1613
		 */
1614
		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...
1615
			$is_active = self::has_instance( $this->_plugin->parent_plugin_id );
1616
1617
			if ( $is_active ) {
1618
				return true;
1619
			}
1620
1621
			/**
1622
			 * Parent module might be a theme. If that's the case, the add-on's FS
1623
			 * instance will be loaded prior to the theme's FS instance, therefore,
1624
			 * we need to check if it's active with a "look ahead".
1625
			 *
1626
			 * @author Vova Feldman
1627
			 * @since  1.2.2.3
1628
			 */
1629
			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...
1630
			if ( is_object( $fs_active_plugins ) && is_array( $fs_active_plugins->plugins ) ) {
1631
				$active_theme = wp_get_theme();
1632
1633
				foreach ( $fs_active_plugins->plugins as $sdk => $module ) {
1634
					if ( WP_FS__MODULE_TYPE_THEME === $module->type ) {
1635
						if ( $module->plugin_path == $active_theme->get_stylesheet() ) {
1636
							// Parent module is a theme and it's currently active.
1637
							return true;
1638
						}
1639
					}
1640
				}
1641
			}
1642
1643
			return false;
1644
		}
1645
1646
		/**
1647
		 * Check if add-on parent plugin in activation mode.
1648
		 *
1649
		 * @author Vova Feldman (@svovaf)
1650
		 * @since  1.0.7
1651
		 *
1652
		 * @return bool
1653
		 */
1654
		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...
1655
			$parent_fs = $this->get_parent_instance();
1656
			if ( ! is_object( $parent_fs ) ) {
1657
				return false;
1658
			}
1659
1660
			return ( $parent_fs->is_activation_mode() );
1661
		}
1662
1663
		/**
1664
		 * Is plugin in activation mode.
1665
		 *
1666
		 * @author Vova Feldman (@svovaf)
1667
		 * @since  1.0.7
1668
		 *
1669
		 * @param bool $and_on
1670
		 *
1671
		 * @return bool
1672
		 */
1673
		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...
1674
			return (
1675
				( $this->is_on() || ! $and_on ) &&
1676
				( ! $this->is_registered() || ( $this->is_only_premium() && ! $this->has_features_enabled_license() ) ) &&
1677
				( ! $this->is_enable_anonymous() ||
1678
				  ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) )
1679
			);
1680
		}
1681
1682
		/**
1683
		 * Check if current page is the opt-in/pending-activation page.
1684
		 *
1685
		 * @author Vova Feldman (@svovaf)
1686
		 * @since  1.2.1.7
1687
		 *
1688
		 * @return bool
1689
		 */
1690
		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...
1691
			if ( $this->_menu->is_main_settings_page() ) {
1692
				return true;
1693
			}
1694
1695
			if ( ! $this->is_activation_mode() ) {
1696
				return false;
1697
			}
1698
1699
			// Check if current page is matching the activation page.
1700
			return $this->is_matching_url( $this->get_activation_url() );
1701
		}
1702
1703
		/**
1704
		 * Check if URL path's are matching and that all querystring
1705
		 * arguments of the $sub_url exist in the $url with the same values.
1706
		 *
1707
		 * WARNING:
1708
		 *  1. This method doesn't check if the sub/domain are matching.
1709
		 *  2. Ignore case sensitivity.
1710
		 *
1711
		 * @author Vova Feldman (@svovaf)
1712
		 * @since  1.2.1.7
1713
		 *
1714
		 * @param string $sub_url
1715
		 * @param string $url     If argument is not set, check if the sub_url matching the current's page URL.
1716
		 *
1717
		 * @return bool
1718
		 */
1719
		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...
1720
			if ( empty( $url ) ) {
1721
				$url = $_SERVER['REQUEST_URI'];
1722
			}
1723
1724
			$url     = strtolower( $url );
1725
			$sub_url = strtolower( $sub_url );
1726
1727
			if ( parse_url( $sub_url, PHP_URL_PATH ) !== parse_url( $url, PHP_URL_PATH ) ) {
1728
				// Different path - DO NOT OVERRIDE PAGE.
1729
				return false;
1730
			}
1731
1732
			$url_params = array();
1733
			parse_str( parse_url( $url, PHP_URL_QUERY ), $url_params );
1734
1735
			$sub_url_params = array();
1736
			parse_str( parse_url( $sub_url, PHP_URL_QUERY ), $sub_url_params );
1737
1738
			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...
1739
				if ( ! isset( $url_params[ $key ] ) || $val != $url_params[ $key ] ) {
1740
					// Not matching query string - DO NOT OVERRIDE PAGE.
1741
					return false;
1742
				}
1743
			}
1744
1745
			return true;
1746
		}
1747
1748
		/**
1749
		 * Get collection of all active plugins.
1750
		 *
1751
		 * @author Vova Feldman (@svovaf)
1752
		 * @since  1.0.9
1753
		 *
1754
		 * @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...
1755
		 */
1756
		private static function get_active_plugins() {
1757
			self::require_plugin_essentials();
1758
1759
			$active_plugin            = array();
1760
			$all_plugins              = get_plugins();
1761
			$active_plugins_basenames = get_option( 'active_plugins' );
1762
1763
			foreach ( $active_plugins_basenames as $plugin_basename ) {
1764
				$active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ];
1765
			}
1766
1767
			return $active_plugin;
1768
		}
1769
1770
		/**
1771
		 * Get collection of all plugins.
1772
		 *
1773
		 * @author Vova Feldman (@svovaf)
1774
		 * @since  1.1.8
1775
		 *
1776
		 * @return array Key is the plugin file path and the value is an array of the plugin data.
1777
		 */
1778
		private static function get_all_plugins() {
1779
			self::require_plugin_essentials();
1780
1781
			$all_plugins              = get_plugins();
1782
			$active_plugins_basenames = get_option( 'active_plugins' );
1783
1784
			foreach ( $all_plugins as $basename => &$data ) {
1785
				// By default set to inactive (next foreach update the active plugins).
1786
				$data['is_active'] = false;
1787
				// Enrich with plugin slug.
1788
				$data['slug'] = self::get_plugin_slug( $basename );
1789
			}
1790
1791
			// Flag active plugins.
1792
			foreach ( $active_plugins_basenames as $basename ) {
1793
				if ( isset( $all_plugins[ $basename ] ) ) {
1794
					$all_plugins[ $basename ]['is_active'] = true;
1795
				}
1796
			}
1797
1798
			return $all_plugins;
1799
		}
1800
1801
1802
		/**
1803
		 * Cached result of get_site_transient( 'update_plugins' )
1804
		 *
1805
		 * @author Vova Feldman (@svovaf)
1806
		 * @since  1.1.8
1807
		 *
1808
		 * @var object
1809
		 */
1810
		private static $_plugins_info;
1811
1812
		/**
1813
		 * Helper function to get specified plugin's slug.
1814
		 *
1815
		 * @author Vova Feldman (@svovaf)
1816
		 * @since  1.1.8
1817
		 *
1818
		 * @param $basename
1819
		 *
1820
		 * @return string
1821
		 */
1822
		private static function get_plugin_slug( $basename ) {
1823
			if ( ! isset( self::$_plugins_info ) ) {
1824
				self::$_plugins_info = get_site_transient( 'update_plugins' );
1825
			}
1826
1827
			$slug = '';
1828
1829
			if ( is_object( self::$_plugins_info ) ) {
1830
				if ( isset( self::$_plugins_info->no_update ) &&
1831
				     isset( self::$_plugins_info->no_update[ $basename ] ) &&
1832
				     ! empty( self::$_plugins_info->no_update[ $basename ]->slug )
1833
				) {
1834
					$slug = self::$_plugins_info->no_update[ $basename ]->slug;
1835
				} else if ( isset( self::$_plugins_info->response ) &&
1836
				            isset( self::$_plugins_info->response[ $basename ] ) &&
1837
				            ! empty( self::$_plugins_info->response[ $basename ]->slug )
1838
				) {
1839
					$slug = self::$_plugins_info->response[ $basename ]->slug;
1840
				}
1841
			}
1842
1843
			if ( empty( $slug ) ) {
1844
				// Try to find slug from FS data.
1845
				$slug = self::find_slug_by_basename( $basename );
1846
			}
1847
1848
			if ( empty( $slug ) ) {
1849
				// Fallback to plugin's folder name.
1850
				$slug = dirname( $basename );
1851
			}
1852
1853
			return $slug;
1854
		}
1855
1856
		private static $_statics_loaded = false;
1857
1858
		/**
1859
		 * Load static resources.
1860
		 *
1861
		 * @author Vova Feldman (@svovaf)
1862
		 * @since  1.0.1
1863
		 */
1864
		private static function _load_required_static() {
1865
			if ( self::$_statics_loaded ) {
1866
				return;
1867
			}
1868
1869
			self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
1870
1871
			self::$_static_logger->entrance();
1872
1873
			self::$_accounts = FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true );
1874
1875
			self::$_global_admin_notices = FS_Admin_Notice_Manager::instance( 'global' );
1876
1877
			add_action( 'admin_menu', array( 'Freemius', '_add_debug_section' ) );
1878
1879
			add_action( "wp_ajax_fs_toggle_debug_mode", array( 'Freemius', '_toggle_debug_mode' ) );
1880
1881
			self::add_ajax_action_static( 'get_debug_log', array( 'Freemius', '_get_debug_log' ) );
1882
1883
			self::add_ajax_action_static( 'get_db_option', array( 'Freemius', '_get_db_option' ) );
1884
1885
			self::add_ajax_action_static( 'set_db_option', array( 'Freemius', '_set_db_option' ) );
1886
1887
			if ( 0 == did_action( 'plugins_loaded' ) ) {
1888
				add_action( 'plugins_loaded', array( 'Freemius', '_load_textdomain' ), 1 );
1889
			}
1890
1891
			self::$_statics_loaded = true;
1892
		}
1893
1894
		#----------------------------------------------------------------------------------
1895
		#region Localization
1896
		#----------------------------------------------------------------------------------
1897
1898
		/**
1899
		 * Load framework's text domain.
1900
		 *
1901
		 * @author Vova Feldman (@svovaf)
1902
		 * @since  1.2.1
1903
		 */
1904
		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...
1905
			if ( ! is_admin() ) {
1906
				return;
1907
			}
1908
1909
			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...
1910
1911
			// Works both for plugins and themes.
1912
			load_plugin_textdomain(
1913
				'freemius',
1914
				false,
1915
				$fs_active_plugins->newest->sdk_path . '/languages/'
1916
			);
1917
		}
1918
1919
		#endregion
1920
1921
		#----------------------------------------------------------------------------------
1922
		#region Debugging
1923
		#----------------------------------------------------------------------------------
1924
1925
		/**
1926
		 * @author Vova Feldman (@svovaf)
1927
		 * @since  1.0.8
1928
		 */
1929
		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...
1930
			if ( ! current_user_can( 'activate_plugins' )
1931
			     && ! current_user_can( 'switch_themes' )
1932
			) {
1933
				return;
1934
			}
1935
1936
			self::$_static_logger->entrance();
1937
1938
			$title = sprintf( '%s [v.%s]', fs_text_inline( 'Freemius Debug' ), WP_FS__SDK_VERSION );
1939
1940
			if ( WP_FS__DEV_MODE ) {
1941
				// Add top-level debug menu item.
1942
				$hook = FS_Admin_Menu_Manager::add_page(
1943
					$title,
1944
					$title,
1945
					'manage_options',
1946
					'freemius',
1947
					array( 'Freemius', '_debug_page_render' )
1948
				);
1949
			} else {
1950
				// Add hidden debug page.
1951
				$hook = FS_Admin_Menu_Manager::add_subpage(
1952
					null,
1953
					$title,
1954
					$title,
1955
					'manage_options',
1956
					'freemius',
1957
					array( 'Freemius', '_debug_page_render' )
1958
				);
1959
			}
1960
1961
			if ( ! empty( $hook ) ) {
1962
				add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) );
1963
			}
1964
		}
1965
1966
		/**
1967
		 * @author Vova Feldman (@svovaf)
1968
		 * @since  1.1.7.3
1969
		 */
1970
		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...
1971
			$is_on = fs_request_get( 'is_on', false, 'post' );
1972
1973
			if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) {
1974
				update_option( 'fs_debug_mode', $is_on );
1975
1976
				// Turn on/off storage logging.
1977
				FS_Logger::_set_storage_logging( ( 1 == $is_on ) );
1978
			}
1979
1980
			exit;
1981
		}
1982
1983
		/**
1984
		 * @author Vova Feldman (@svovaf)
1985
		 * @since  1.2.1.6
1986
		 */
1987
		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...
1988
			$logs = FS_Logger::load_db_logs(
1989
				fs_request_get( 'filters', false, 'post' ),
1990
				! empty( $_POST['limit'] ) && is_numeric( $_POST['limit'] ) ? $_POST['limit'] : 200,
1991
				! empty( $_POST['offset'] ) && is_numeric( $_POST['offset'] ) ? $_POST['offset'] : 0
1992
			);
1993
1994
			self::shoot_ajax_success( $logs );
1995
		}
1996
1997
		/**
1998
		 * @author Vova Feldman (@svovaf)
1999
		 * @since  1.2.1.7
2000
		 */
2001
		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...
2002
			$option_name = fs_request_get( 'option_name' );
2003
2004
			$value = get_option( $option_name );
2005
2006
			$result = array(
2007
				'name' => $option_name,
2008
			);
2009
2010
			if ( false !== $value ) {
2011
				if ( ! is_string( $value ) ) {
2012
					$value = json_encode( $value );
2013
				}
2014
2015
				$result['value'] = $value;
2016
			}
2017
2018
			self::shoot_ajax_success( $result );
2019
		}
2020
2021
		/**
2022
		 * @author Vova Feldman (@svovaf)
2023
		 * @since  1.2.1.7
2024
		 */
2025
		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...
2026
			$option_name  = fs_request_get( 'option_name' );
2027
			$option_value = fs_request_get( 'option_value' );
2028
2029
			if ( ! empty( $option_value ) ) {
2030
				update_option( $option_name, $option_value );
2031
			}
2032
2033
			self::shoot_ajax_success();
2034
		}
2035
2036
		/**
2037
		 * @author Vova Feldman (@svovaf)
2038
		 * @since  1.0.8
2039
		 */
2040
		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...
2041
			self::_clean_admin_content_section();
2042
2043
			if ( fs_request_is_action( 'restart_freemius' ) ) {
2044
				check_admin_referer( 'restart_freemius' );
2045
2046
				// Clear accounts data.
2047
				self::$_accounts->clear( true );
2048
2049
				// Clear SDK reference cache.
2050
				delete_option( 'fs_active_plugins' );
2051
			} else if ( fs_request_is_action( 'simulate_trial' ) ) {
2052
				check_admin_referer( 'simulate_trial' );
2053
2054
				$fs = freemius( fs_request_get( 'module_id' ) );
2055
2056
				// Update SDK install to at least 24 hours before.
2057
				$fs->_storage->install_timestamp = ( time() - WP_FS__TIME_24_HOURS_IN_SEC );
2058
				// Unset the trial shown timestamp.
2059
				unset( $fs->_storage->trial_promotion_shown );
2060
			} else if ( fs_request_is_action( 'delete_install' ) ) {
2061
				check_admin_referer( 'delete_install' );
2062
2063
				self::_delete_site_by_slug(
2064
					fs_request_get( 'slug' ),
2065
					fs_request_get( 'module_type' )
2066
				);
2067
			} else if ( fs_request_is_action( 'download_logs' ) ) {
2068
				check_admin_referer( 'download_logs' );
2069
2070
				$download_url = FS_Logger::download_db_logs(
2071
					fs_request_get( 'filters', false, 'post' )
2072
				);
2073
2074
				if ( false === $download_url ) {
2075
					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].' );
2076
				}
2077
2078
				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 2070 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...
2079
			}
2080
		}
2081
2082
		/**
2083
		 * @author Vova Feldman (@svovaf)
2084
		 * @since  1.0.8
2085
		 */
2086
		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...
2087
			self::$_static_logger->entrance();
2088
2089
			$vars = array(
2090
				'plugin_sites'    => self::get_all_sites(),
2091
				'theme_sites'     => self::get_all_sites( WP_FS__MODULE_TYPE_THEME ),
2092
				'users'           => self::get_all_users(),
2093
				'addons'          => self::get_all_addons(),
2094
				'account_addons'  => self::get_all_account_addons(),
2095
				'plugin_licenses' => self::get_all_licenses(),
2096
				'theme_licenses'  => self::get_all_licenses( WP_FS__MODULE_TYPE_THEME )
2097
			);
2098
2099
			fs_enqueue_local_style( 'fs_debug', '/admin/debug.css' );
2100
			fs_require_once_template( 'debug.php', $vars );
2101
		}
2102
2103
		#endregion
2104
2105
		#----------------------------------------------------------------------------------
2106
		#region Connectivity Issues
2107
		#----------------------------------------------------------------------------------
2108
2109
		/**
2110
		 * Check if Freemius should be turned on for the current plugin install.
2111
		 *
2112
		 * Note:
2113
		 *  $this->_is_on is updated in has_api_connectivity()
2114
		 *
2115
		 * @author Vova Feldman (@svovaf)
2116
		 * @since  1.0.9
2117
		 *
2118
		 * @return bool
2119
		 */
2120
		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...
2121
			self::$_static_logger->entrance();
2122
2123
			if ( isset( $this->_is_on ) ) {
2124
				return $this->_is_on;
2125
			}
2126
2127
			// If already installed or pending then sure it's on :)
2128
			if ( $this->is_registered() || $this->is_pending_activation() ) {
2129
				$this->_is_on = true;
2130
2131
				return true;
2132
			}
2133
2134
			return false;
2135
		}
2136
2137
		/**
2138
		 * @author Vova Feldman (@svovaf)
2139
		 * @since  1.1.7.3
2140
		 *
2141
		 * @param bool $flush_if_no_connectivity
2142
		 *
2143
		 * @return bool
2144
		 */
2145
		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...
2146
			if ( ! isset( $this->_storage->connectivity_test ) ) {
2147
				// Connectivity test was never executed, or cache was cleared.
2148
				return true;
2149
			}
2150
2151
			if ( WP_FS__PING_API_ON_IP_OR_HOST_CHANGES ) {
2152
				if ( WP_FS__IS_HTTP_REQUEST ) {
2153
					if ( $_SERVER['HTTP_HOST'] != $this->_storage->connectivity_test['host'] ) {
2154
						// Domain changed.
2155
						return true;
2156
					}
2157
2158
					if ( WP_FS__REMOTE_ADDR != $this->_storage->connectivity_test['server_ip'] ) {
2159
						// Server IP changed.
2160
						return true;
2161
					}
2162
				}
2163
			}
2164
2165
			if ( $this->_storage->connectivity_test['is_connected'] &&
2166
			     $this->_storage->connectivity_test['is_active']
2167
			) {
2168
				// API connected and Freemius is active - no need to run connectivity check.
2169
				return false;
2170
			}
2171
2172
			if ( $flush_if_no_connectivity ) {
2173
				/**
2174
				 * If explicitly asked to flush when no connectivity - do it only
2175
				 * if at least 10 sec passed from the last API connectivity test.
2176
				 */
2177
				return ( isset( $this->_storage->connectivity_test['timestamp'] ) &&
2178
				         ( WP_FS__SCRIPT_START_TIME - $this->_storage->connectivity_test['timestamp'] ) > 10 );
2179
			}
2180
2181
			/**
2182
			 * @since 1.1.7 Don't check for connectivity on plugin downgrade.
2183
			 */
2184
			$version = $this->get_plugin_version();
2185
			if ( version_compare( $version, $this->_storage->connectivity_test['version'], '>' ) ) {
2186
				// If it's a plugin version upgrade and Freemius is off or no connectivity, run connectivity test.
2187
				return true;
2188
			}
2189
2190
			return false;
2191
		}
2192
2193
		/**
2194
		 * @author Vova Feldman (@svovaf)
2195
		 * @since  1.1.7.4
2196
		 *
2197
		 * @return object|false
2198
		 */
2199
		private function ping() {
2200
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) {
2201
				return false;
2202
			}
2203
2204
			$version = $this->get_plugin_version();
2205
2206
			$is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() );
2207
2208
			return $this->get_api_plugin_scope()->ping(
2209
				$this->get_anonymous_id(),
2210
				array(
2211
					'is_update' => json_encode( $is_update ),
2212
					'version'   => $version,
2213
					'sdk'       => $this->version,
2214
					'is_admin'  => json_encode( is_admin() ),
2215
					'is_ajax'   => json_encode( self::is_ajax() ),
2216
					'is_cron'   => json_encode( self::is_cron() ),
2217
					'is_http'   => json_encode( WP_FS__IS_HTTP_REQUEST ),
2218
				)
2219
			);
2220
		}
2221
2222
		/**
2223
		 * Check if there's any connectivity issue to Freemius API.
2224
		 *
2225
		 * @author Vova Feldman (@svovaf)
2226
		 * @since  1.0.9
2227
		 *
2228
		 * @param bool $flush_if_no_connectivity
2229
		 *
2230
		 * @return bool
2231
		 */
2232
		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...
2233
			$this->_logger->entrance();
2234
2235
			if ( isset( $this->_has_api_connection ) && ( $this->_has_api_connection || ! $flush_if_no_connectivity ) ) {
2236
				return $this->_has_api_connection;
2237
			}
2238
2239
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY &&
2240
			     isset( $this->_storage->connectivity_test ) &&
2241
			     true === $this->_storage->connectivity_test['is_connected']
2242
			) {
2243
				unset( $this->_storage->connectivity_test );
2244
			}
2245
2246
			if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) {
2247
				$this->_has_api_connection = $this->_storage->connectivity_test['is_connected'];
2248
				/**
2249
				 * @since 1.1.6 During dev mode, if there's connectivity - turn Freemius on regardless the configuration.
2250
				 *
2251
				 * @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.
2252
				 */
2253
				$this->_is_on = $this->_storage->connectivity_test['is_active'] ||
2254
				                $this->is_premium() ||
2255
				                ( WP_FS__DEV_MODE && $this->_has_api_connection && ! WP_FS__SIMULATE_FREEMIUS_OFF );
2256
2257
				return $this->_has_api_connection;
2258
			}
2259
2260
			$pong         = $this->ping();
2261
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2262
2263
			if ( ! $is_connected ) {
2264
				// API failure.
2265
				$this->_add_connectivity_issue_message( $pong );
2266
			}
2267
2268
			$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 2260 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...
2269
2270
			return $this->_has_api_connection;
2271
		}
2272
2273
		/**
2274
		 * @author Vova Feldman (@svovaf)
2275
		 * @since  1.1.7.4
2276
		 *
2277
		 * @param object $pong
2278
		 * @param bool   $is_connected
2279
		 */
2280
		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...
2281
			$this->_logger->entrance();
2282
2283
			$version = $this->get_plugin_version();
2284
2285
			if ( ! $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) {
2286
				$is_active = false;
2287
			} else {
2288
				$is_active = ( isset( $pong->is_active ) && true == $pong->is_active );
2289
			}
2290
2291
			$is_active = $this->apply_filters(
2292
				'is_on',
2293
				$is_active,
2294
				$this->is_plugin_update(),
2295
				$version
2296
			);
2297
2298
			$this->_storage->connectivity_test = array(
2299
				'is_connected' => $is_connected,
2300
				'host'         => $_SERVER['HTTP_HOST'],
2301
				'server_ip'    => WP_FS__REMOTE_ADDR,
2302
				'is_active'    => $is_active,
2303
				'timestamp'    => WP_FS__SCRIPT_START_TIME,
2304
				// Last version with connectivity attempt.
2305
				'version'      => $version,
2306
			);
2307
2308
			$this->_has_api_connection = $is_connected;
2309
			$this->_is_on              = $is_active || ( WP_FS__DEV_MODE && $is_connected && ! WP_FS__SIMULATE_FREEMIUS_OFF );
2310
		}
2311
2312
		/**
2313
		 * Force turning Freemius on.
2314
		 *
2315
		 * @author Vova Feldman (@svovaf)
2316
		 * @since  1.1.8.1
2317
		 *
2318
		 * @return bool TRUE if successfully turned on.
2319
		 */
2320
		private function turn_on() {
2321
			$this->_logger->entrance();
2322
2323
			if ( $this->is_on() || ! isset( $this->_storage->connectivity_test['is_active'] ) ) {
2324
				return false;
2325
			}
2326
2327
			$updated_connectivity              = $this->_storage->connectivity_test;
2328
			$updated_connectivity['is_active'] = true;
2329
			$updated_connectivity['timestamp'] = WP_FS__SCRIPT_START_TIME;
2330
			$this->_storage->connectivity_test = $updated_connectivity;
2331
2332
			$this->_is_on = true;
2333
2334
			return true;
2335
		}
2336
2337
		/**
2338
		 * Anonymous and unique site identifier (Hash).
2339
		 *
2340
		 * @author Vova Feldman (@svovaf)
2341
		 * @since  1.1.0
2342
		 *
2343
		 * @return string
2344
		 */
2345
        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...
2346
            $unique_id = self::$_accounts->get_option( 'unique_id' );
2347
2348
            if ( empty( $unique_id ) || ! is_string( $unique_id ) ) {
2349
                $key = get_site_url();
2350
2351
                // If localhost, assign microtime instead of domain.
2352
                if ( WP_FS__IS_LOCALHOST ||
2353
                     false !== strpos( $key, 'localhost' ) ||
2354
                     false === strpos( $key, '.' )
2355
                ) {
2356
                    $key = microtime();
2357
                }
2358
2359
                /**
2360
                 * Base the unique identifier on the WP secure authentication key. Which
2361
                 * turns the key into a secret anonymous identifier.
2362
                 *
2363
                 * @author Vova Feldman (@svovaf)
2364
                 * @since 1.2.3
2365
                 */
2366
                $unique_id = md5( $key . SECURE_AUTH_KEY );
2367
2368
                self::$_accounts->set_option( 'unique_id', $unique_id, true );
2369
            }
2370
2371
            $this->_logger->departure( $unique_id );
2372
2373
            return $unique_id;
2374
        }
2375
2376
		/**
2377
		 * @author Vova Feldman (@svovaf)
2378
		 * @since  1.1.7.4
2379
		 *
2380
		 * @return \WP_User
2381
		 */
2382
		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...
2383
			self::require_pluggable_essentials();
2384
2385
			return wp_get_current_user();
2386
		}
2387
2388
		/**
2389
		 * @author Vova Feldman (@svovaf)
2390
		 * @since  1.2.1.7
2391
		 *
2392
		 * @param string $email
2393
		 *
2394
		 * @return bool
2395
		 */
2396
		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...
2397
			if ( false === filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
2398
				return false;
2399
			}
2400
2401
			$parts = explode( '@', $email );
2402
2403
			if ( 2 !== count( $parts ) || empty( $parts[1] ) ) {
2404
				return false;
2405
			}
2406
2407
			$blacklist = array(
2408
				'admin.',
2409
				'webmaster.',
2410
				'localhost.',
2411
				'dev.',
2412
				'development.',
2413
				'test.',
2414
				'stage.',
2415
				'staging.',
2416
			);
2417
2418
			// Make sure domain is not one of the blacklisted.
2419
			foreach ( $blacklist as $invalid ) {
2420
				if ( 0 === strpos( $parts[1], $invalid ) ) {
2421
					return false;
2422
				}
2423
			}
2424
2425
			// Get the UTF encoded domain name.
2426
			$domain = idn_to_ascii( $parts[1] ) . '.';
2427
2428
			return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) );
2429
		}
2430
		
2431
		/**
2432
		 * Generate API connectivity issue message.
2433
		 *
2434
		 * @author Vova Feldman (@svovaf)
2435
		 * @since  1.0.9
2436
		 *
2437
		 * @param mixed $api_result
2438
		 * @param bool  $is_first_failure
2439
		 */
2440
		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...
2441
			if ( ! $this->is_premium() && $this->_enable_anonymous ) {
2442
				// Don't add message if it's the free version and can run anonymously.
2443
				return;
2444
			}
2445
2446
			if ( ! function_exists( 'wp_nonce_url' ) ) {
2447
				require_once ABSPATH . 'wp-includes/functions.php';
2448
			}
2449
2450
			$current_user = self::_get_current_wp_user();
2451
//			$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...
2452
			$admin_email = $current_user->user_email;
2453
2454
			// Aliases.
2455
            $deactivate_plugin_title  = $this->esc_html_inline( 'That\'s exhausting, please deactivate', 'deactivate-plugin-title' );
2456
            $deactivate_plugin_desc   = $this->esc_html_inline( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.', 'deactivate-plugin-desc' );
2457
            $install_previous_title   = $this->esc_html_inline( 'Let\'s try your previous version', 'install-previous-title' );
2458
            $install_previous_desc    = $this->esc_html_inline( 'Uninstall this version and install the previous one.', 'install-previous-desc' );
2459
            $fix_issue_title          = $this->esc_html_inline( 'Yes - I\'m giving you a chance to fix it', 'fix-issue-title' );
2460
            $fix_issue_desc           = $this->esc_html_inline( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.', 'fix-issue-desc' );
2461
            /* translators: %s: product title (e.g. "Awesome Plugin" requires an access to...) */
2462
            $x_requires_access_to_api = $this->esc_html_inline( '%s requires an access to our API.', 'x-requires-access-to-api' );
2463
            $sysadmin_title = $this->esc_html_inline( 'I\'m a system administrator', 'sysadmin-title' );
2464
            $happy_to_resolve_issue_asap = $this->esc_html_inline( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.', 'happy-to-resolve-issue-asap' );
2465
2466
			$message = false;
2467
			if ( is_object( $api_result ) &&
2468
			     isset( $api_result->error ) &&
2469
			     isset( $api_result->error->code )
2470
			) {
2471
				switch ( $api_result->error->code ) {
2472
					case 'curl_missing':
2473
						$missing_methods = '';
2474
						if ( is_array( $api_result->missing_methods ) &&
2475
						     ! empty( $api_result->missing_methods )
2476
						) {
2477
							foreach ( $api_result->missing_methods as $m ) {
2478
								if ( 'curl_version' === $m ) {
2479
									continue;
2480
								}
2481
2482
								if ( ! empty( $missing_methods ) ) {
2483
									$missing_methods .= ', ';
2484
								}
2485
2486
								$missing_methods .= sprintf( '<code>%s</code>', $m );
2487
							}
2488
2489
							if ( ! empty( $missing_methods ) ) {
2490
								$missing_methods = sprintf(
2491
									'<br><br><b>%s</b> %s',
2492
									$this->esc_html_inline( 'Disabled method(s):', 'curl-disabled-methods' ),
2493
									$missing_methods
2494
								);
2495
							}
2496
						}
2497
2498
						$message = sprintf(
2499
                            $x_requires_access_to_api . ' ' .
2500
							$this->esc_html_inline( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.', 'curl-missing-message' ) . ' ' .
2501
							$missing_methods .
2502
							' %s',
2503
							'<b>' . $this->get_plugin_name() . '</b>',
2504
							sprintf(
2505
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2506
								sprintf(
2507
									'<a class="fs-resolve" data-type="curl" href="#"><b>%s</b></a>%s',
2508
									$this->get_text_inline( 'I don\'t know what is cURL or how to install it, help me!', 'curl-missing-no-clue-title' ),
2509
									' - ' . sprintf(
2510
										$this->get_text_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'curl-missing-no-clue-desc' ),
2511
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2512
									)
2513
								),
2514
								sprintf(
2515
									'<b>%s</b> - %s',
2516
                                    $sysadmin_title,
2517
									esc_html( sprintf( $this->get_text_inline( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.', 'curl-missing-sysadmin-desc' ), $this->get_module_label( true ) ) )
2518
								),
2519
								sprintf(
2520
									'<a href="%s"><b>%s</b></a> - %s',
2521
									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 ),
2522
									$deactivate_plugin_title,
2523
									$deactivate_plugin_desc
2524
								)
2525
							)
2526
						);
2527
						break;
2528
					case 'cloudflare_ddos_protection':
2529
						$message = sprintf(
2530
                            $x_requires_access_to_api . ' ' .
2531
							$this->esc_html_inline( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.', 'cloudflare-blocks-connection-message' ) . ' ' .
2532
                            $happy_to_resolve_issue_asap .
2533
							' %s',
2534
							'<b>' . $this->get_plugin_name() . '</b>',
2535
							sprintf(
2536
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2537
								sprintf(
2538
									'<a class="fs-resolve" data-type="cloudflare" href="#"><b>%s</b></a>%s',
2539
									$fix_issue_title,
2540
									' - ' . sprintf(
2541
										$fix_issue_desc,
2542
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2543
									)
2544
								),
2545
								sprintf(
2546
									'<a href="%s" target="_blank"><b>%s</b></a> - %s',
2547
									sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
2548
									$install_previous_title,
2549
									$install_previous_desc
2550
								),
2551
								sprintf(
2552
									'<a href="%s"><b>%s</b></a> - %s',
2553
									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 ),
2554
									$deactivate_plugin_title,
2555
									$deactivate_plugin_desc
2556
								)
2557
							)
2558
						);
2559
						break;
2560
					case 'squid_cache_block':
2561
						$message = sprintf(
2562
                            $x_requires_access_to_api . ' ' .
2563
							$this->esc_html_inline( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.', 'squid-blocks-connection-message' ) .
2564
							' %s',
2565
							'<b>' . $this->get_plugin_name() . '</b>',
2566
							sprintf(
2567
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2568
								sprintf(
2569
									'<a class="fs-resolve" data-type="squid" href="#"><b>%s</b></a> - %s',
2570
									$this->esc_html_inline( 'I don\'t know what is Squid or ACL, help me!', 'squid-no-clue-title' ),
2571
									sprintf(
2572
										$this->esc_html_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'squid-no-clue-desc' ),
2573
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2574
									)
2575
								),
2576
								sprintf(
2577
									'<b>%s</b> - %s',
2578
                                    $sysadmin_title,
2579
									sprintf(
2580
										$this->esc_html_inline( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.', 'squid-sysadmin-desc' ),
2581
										// We use a filter since the plugin might require additional API connectivity.
2582
										'<b>' . implode( ', ', $this->apply_filters( 'api_domains', array( 'api.freemius.com', 'wp.freemius.com' ) ) ) . '</b>',
2583
										$this->_module_type
2584
									)
2585
								),
2586
								sprintf(
2587
									'<a href="%s"><b>%s</b></a> - %s',
2588
									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 ),
2589
									$deactivate_plugin_title,
2590
									$deactivate_plugin_desc
2591
								)
2592
							)
2593
						);
2594
						break;
2595
//					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...
2596
//						$message = $this->get_text_inline( 'connectivity-test-fails-message' );
2597
//						break;
2598
				}
2599
			}
2600
2601
			$message_id = 'failed_connect_api';
2602
			$type       = 'error';
2603
2604
            $connectivity_test_fails_message = $this->esc_html_inline( 'From unknown reason, the API connectivity test failed.', 'connectivity-test-fails-message' );
2605
2606
			if ( false === $message ) {
2607
				if ( $is_first_failure ) {
2608
					// First attempt failed.
2609
					$message = sprintf(
2610
                        $x_requires_access_to_api . ' ' .
2611
                        $connectivity_test_fails_message . ' ' .
2612
						$this->esc_html_inline( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?', 'connectivity-test-maybe-temporary' ) . '<br><br>' .
2613
						'%s',
2614
						'<b>' . $this->get_plugin_name() . '</b>',
2615
						sprintf(
2616
							'<div id="fs_firewall_issue_options">%s %s</div>',
2617
							sprintf(
2618
								'<a  class="button button-primary fs-resolve" data-type="retry_ping" href="#">%s</a>',
2619
								$this->get_text_inline( 'Yes - do your thing', 'yes-do-your-thing' )
2620
							),
2621
							sprintf(
2622
								'<a href="%s" class="button">%s</a>',
2623
								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 ),
2624
								$this->get_text_inline( 'No - just deactivate', 'no-deactivate' )
2625
							)
2626
						)
2627
					);
2628
2629
					$message_id = 'failed_connect_api_first';
2630
					$type       = 'promotion';
2631
				} else {
2632
					// Second connectivity attempt failed.
2633
					$message = sprintf(
2634
                        $x_requires_access_to_api . ' ' .
2635
                        $connectivity_test_fails_message . ' ' .
2636
                        $happy_to_resolve_issue_asap .
2637
						' %s',
2638
						'<b>' . $this->get_plugin_name() . '</b>',
2639
						sprintf(
2640
							'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2641
							sprintf(
2642
								'<a class="fs-resolve" data-type="general" href="#"><b>%s</b></a>%s',
2643
								$fix_issue_title,
2644
								' - ' . sprintf(
2645
									$fix_issue_desc,
2646
									'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2647
								)
2648
							),
2649
							sprintf(
2650
								'<a href="%s" target="_blank"><b>%s</b></a> - %s',
2651
								sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
2652
								$install_previous_title,
2653
								$install_previous_desc
2654
							),
2655
							sprintf(
2656
								'<a href="%s"><b>%s</b></a> - %s',
2657
								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 ),
2658
								$deactivate_plugin_title,
2659
								$deactivate_plugin_desc
2660
							)
2661
						)
2662
					);
2663
				}
2664
			}
2665
2666
			$this->_admin_notices->add_sticky(
2667
				$message,
2668
				$message_id,
2669
				$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
2670
				$type
2671
			);
2672
		}
2673
2674
		/**
2675
		 * Handle user request to resolve connectivity issue.
2676
		 * This method will send an email to Freemius API technical staff for resolution.
2677
		 * The email will contain server's info and installed plugins (might be caching issue).
2678
		 *
2679
		 * @author Vova Feldman (@svovaf)
2680
		 * @since  1.0.9
2681
		 */
2682
		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...
2683
			$this->_admin_notices->remove_sticky( 'failed_connect_api' );
2684
2685
			$pong = $this->ping();
2686
2687
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2688
2689
			if ( $is_connected ) {
2690
				$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 2685 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...
2691
2692
				echo $this->get_after_plugin_activation_redirect_url();
2693
				exit;
2694
			}
2695
2696
			$current_user = self::_get_current_wp_user();
2697
			$admin_email  = $current_user->user_email;
2698
2699
			$error_type = fs_request_get( 'error_type', 'general' );
2700
2701
			switch ( $error_type ) {
2702
				case 'squid':
2703
					$title = 'Squid ACL Blocking Issue';
2704
					break;
2705
				case 'cloudflare':
2706
					$title = 'CloudFlare Blocking Issue';
2707
					break;
2708
				default:
2709
					$title = 'API Connectivity Issue';
2710
					break;
2711
			}
2712
2713
			$custom_email_sections = array();
2714
2715
			// Add 'API Error' custom email section.
2716
			$custom_email_sections['api_error'] = array(
2717
				'title' => 'API Error',
2718
				'rows'  => array(
2719
					'ping' => array(
2720
						'API Error',
2721
						is_string( $pong ) ? htmlentities( $pong ) : json_encode( $pong )
2722
					),
2723
				)
2724
			);
2725
2726
			// Send email with technical details to resolve API connectivity issues.
2727
			$this->send_email(
2728
				'[email protected]',                              // recipient
2729
				$title . ' [' . $this->get_plugin_name() . ']',  // subject
2730
				$custom_email_sections,
2731
				array( "Reply-To: $admin_email <$admin_email>" ) // headers
2732
			);
2733
2734
			$this->_admin_notices->add_sticky(
2735
				sprintf(
2736
					$this->get_text_inline( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.', 'fix-request-sent-message' ),
2737
					'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2738
				),
2739
				'server_details_sent'
2740
			);
2741
2742
			// Action was taken, tell that API connectivity troubleshooting should be off now.
2743
2744
			echo "1";
2745
			exit;
2746
		}
2747
2748
		/**
2749
		 * Handle connectivity test retry approved by the user.
2750
		 *
2751
		 * @author Vova Feldman (@svovaf)
2752
		 * @since  1.1.7.4
2753
		 */
2754
		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...
2755
			$this->_admin_notices->remove_sticky( 'failed_connect_api_first' );
2756
2757
			$pong = $this->ping();
2758
2759
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2760
2761
			if ( $is_connected ) {
2762
				$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 2757 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...
2763
2764
				echo $this->get_after_plugin_activation_redirect_url();
2765
			} else {
2766
				// Add connectivity issue message after 2nd failed attempt.
2767
				$this->_add_connectivity_issue_message( $pong, false );
2768
2769
				echo "1";
2770
			}
2771
2772
			exit;
2773
		}
2774
2775
		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...
2776
			$params = array();
2777
			fs_require_once_template( 'firewall-issues-js.php', $params );
2778
		}
2779
2780
		#endregion
2781
2782
		#----------------------------------------------------------------------------------
2783
		#region Email
2784
		#----------------------------------------------------------------------------------
2785
2786
		/**
2787
		 * Generates and sends an HTML email with customizable sections.
2788
		 *
2789
		 * @author Leo Fajardo (@leorw)
2790
		 * @since  1.1.2
2791
		 *
2792
		 * @param string $to_address
2793
		 * @param string $subject
2794
		 * @param array  $sections
2795
		 * @param array  $headers
2796
		 *
2797
		 * @return bool Whether the email contents were sent successfully.
2798
		 */
2799
		private function send_email(
2800
			$to_address,
2801
			$subject,
2802
			$sections = array(),
2803
			$headers = array()
2804
		) {
2805
			$default_sections = $this->get_email_sections();
2806
2807
			// Insert new sections or replace the default email sections.
2808
			if ( is_array( $sections ) && ! empty( $sections ) ) {
2809
				foreach ( $sections as $section_id => $custom_section ) {
2810
					if ( ! isset( $default_sections[ $section_id ] ) ) {
2811
						// If the section does not exist, add it.
2812
						$default_sections[ $section_id ] = $custom_section;
2813
					} else {
2814
						// If the section already exists, override it.
2815
						$current_section = $default_sections[ $section_id ];
2816
2817
						// Replace the current section's title if a custom section title exists.
2818
						if ( isset( $custom_section['title'] ) ) {
2819
							$current_section['title'] = $custom_section['title'];
2820
						}
2821
2822
						// Insert new rows under the current section or replace the default rows.
2823
						if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) {
2824
							foreach ( $custom_section['rows'] as $row_id => $row ) {
2825
								$current_section['rows'][ $row_id ] = $row;
2826
							}
2827
						}
2828
2829
						$default_sections[ $section_id ] = $current_section;
2830
					}
2831
				}
2832
			}
2833
2834
			$vars    = array( 'sections' => $default_sections );
2835
			$message = fs_get_template( 'email.php', $vars );
2836
2837
			// Set the type of email to HTML.
2838
			$headers[] = 'Content-type: text/html; charset=UTF-8';
2839
2840
			$header_string = implode( "\r\n", $headers );
2841
2842
			return wp_mail(
2843
				$to_address,
2844
				$subject,
2845
				$message,
2846
				$header_string
2847
			);
2848
		}
2849
2850
		/**
2851
		 * Generates the data for the sections of the email content.
2852
		 *
2853
		 * @author Leo Fajardo (@leorw)
2854
		 * @since  1.1.2
2855
		 *
2856
		 * @return array
2857
		 */
2858
		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...
2859
			// Retrieve the current user's information so that we can get the user's email, first name, and last name below.
2860
			$current_user = self::_get_current_wp_user();
2861
2862
			// Retrieve the cURL version information so that we can get the version number below.
2863
			$curl_version_information = curl_version();
2864
2865
			$active_plugin = self::get_active_plugins();
2866
2867
			// Generate the list of active plugins separated by new line.
2868
			$active_plugin_string = '';
2869
			foreach ( $active_plugin as $plugin ) {
2870
				$active_plugin_string .= sprintf(
2871
					'<a href="%s">%s</a> [v%s]<br>',
2872
					$plugin['PluginURI'],
2873
					$plugin['Name'],
2874
					$plugin['Version']
2875
				);
2876
			}
2877
2878
			$server_ip = WP_FS__REMOTE_ADDR;
2879
2880
			// Add PHP info for deeper investigation.
2881
			ob_start();
2882
			phpinfo();
2883
			$php_info = ob_get_clean();
2884
2885
			$api_domain = substr( FS_API__ADDRESS, strpos( FS_API__ADDRESS, ':' ) + 3 );
2886
2887
			// Generate the default email sections.
2888
			$sections = array(
2889
				'sdk'      => array(
2890
					'title' => 'SDK',
2891
					'rows'  => array(
2892
						'fs_version'   => array( 'FS Version', $this->version ),
2893
						'curl_version' => array( 'cURL Version', $curl_version_information['version'] )
2894
					)
2895
				),
2896
				'plugin'   => array(
2897
					'title' => ucfirst( $this->get_module_type() ),
2898
					'rows'  => array(
2899
						'name'    => array( 'Name', $this->get_plugin_name() ),
2900
						'version' => array( 'Version', $this->get_plugin_version() )
2901
					)
2902
				),
2903
				'api'      => array(
2904
					'title' => 'API Subdomain',
2905
					'rows'  => array(
2906
						'dns' => array(
2907
							'DNS_CNAME',
2908
							function_exists( 'dns_get_record' ) ?
2909
								var_export( dns_get_record( $api_domain, DNS_CNAME ), true ) :
2910
								'dns_get_record() disabled/blocked'
2911
						),
2912
						'ip'  => array(
2913
							'IP',
2914
							function_exists( 'gethostbyname' ) ?
2915
								gethostbyname( $api_domain ) :
2916
								'gethostbyname() disabled/blocked'
2917
						),
2918
					),
2919
				),
2920
				'site'     => array(
2921
					'title' => 'Site',
2922
					'rows'  => array(
2923
						'unique_id'   => array( 'Unique ID', $this->get_anonymous_id() ),
2924
						'address'     => array( 'Address', site_url() ),
2925
						'host'        => array(
2926
							'HTTP_HOST',
2927
							( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' )
2928
						),
2929
						'hosting'     => array(
2930
							'Hosting Company' => fs_request_has( 'hosting_company' ) ?
2931
								fs_request_get( 'hosting_company' ) :
2932
								'Unknown',
2933
						),
2934
						'server_addr' => array(
2935
							'SERVER_ADDR',
2936
							'<a href="http://www.projecthoneypot.org/ip_' . $server_ip . '">' . $server_ip . '</a>'
2937
						)
2938
					)
2939
				),
2940
				'user'     => array(
2941
					'title' => 'User',
2942
					'rows'  => array(
2943
						'email' => array( 'Email', $current_user->user_email ),
2944
						'first' => array( 'First', $current_user->user_firstname ),
2945
						'last'  => array( 'Last', $current_user->user_lastname )
2946
					)
2947
				),
2948
				'plugins'  => array(
2949
					'title' => 'Plugins',
2950
					'rows'  => array(
2951
						'active_plugins' => array( 'Active Plugins', $active_plugin_string )
2952
					)
2953
				),
2954
				'php_info' => array(
2955
					'title' => 'PHP Info',
2956
					'rows'  => array(
2957
						'info' => array( $php_info )
2958
					),
2959
				)
2960
			);
2961
2962
			// Allow the sections to be modified by other code.
2963
			$sections = $this->apply_filters( 'email_template_sections', $sections );
2964
2965
			return $sections;
2966
		}
2967
2968
		#endregion
2969
2970
		#----------------------------------------------------------------------------------
2971
		#region Initialization
2972
		#----------------------------------------------------------------------------------
2973
2974
		/**
2975
		 * Init plugin's Freemius instance.
2976
		 *
2977
		 * @author Vova Feldman (@svovaf)
2978
		 * @since  1.0.1
2979
		 *
2980
		 * @param number $id
2981
		 * @param string $public_key
2982
		 * @param bool   $is_live
2983
		 * @param bool   $is_premium
2984
		 */
2985
		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...
2986
			$this->_logger->entrance();
2987
2988
			$this->dynamic_init( array(
2989
				'id'         => $id,
2990
				'public_key' => $public_key,
2991
				'is_live'    => $is_live,
2992
				'is_premium' => $is_premium,
2993
			) );
2994
		}
2995
2996
		/**
2997
		 * Dynamic initiator, originally created to support initiation
2998
		 * with parent_id for add-ons.
2999
		 *
3000
		 * @author Vova Feldman (@svovaf)
3001
		 * @since  1.0.6
3002
		 *
3003
		 * @param array $plugin_info
3004
		 *
3005
		 * @throws Freemius_Exception
3006
		 */
3007
		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...
3008
			$this->_logger->entrance();
3009
3010
			$this->parse_settings( $plugin_info );
3011
3012
            if ( ! self::is_ajax() ) {
3013
                if ( ! $this->is_addon() || $this->is_only_premium() ) {
3014
                    add_action( 'admin_menu', array( &$this, '_prepare_admin_menu' ), WP_FS__LOWEST_PRIORITY );
3015
                }
3016
            }
3017
3018
            if ( $this->should_stop_execution() ) {
3019
				return;
3020
			}
3021
3022
			if ( ! $this->is_registered() ) {
3023
				if ( $this->is_anonymous() ) {
3024
					// If user skipped, no need to test connectivity.
3025
					$this->_has_api_connection = true;
3026
					$this->_is_on              = true;
3027
				} else {
3028
					if ( ! $this->has_api_connectivity() ) {
3029
						if ( $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) ||
3030
						     $this->_admin_notices->has_sticky( 'failed_connect_api' )
3031
						) {
3032
							if ( ! $this->_enable_anonymous || $this->is_premium() ) {
3033
								// If anonymous mode is disabled, add firewall admin-notice message.
3034
								add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) );
3035
3036
								$this->add_ajax_action( 'resolve_firewall_issues', array(
3037
									&$this,
3038
									'_email_about_firewall_issue'
3039
								) );
3040
3041
								$this->add_ajax_action( 'retry_connectivity_test', array(
3042
									&$this,
3043
									'_retry_connectivity_test'
3044
								) );
3045
							}
3046
						}
3047
3048
						return;
3049
					} else {
3050
						$this->_admin_notices->remove_sticky( array(
3051
							'failed_connect_api_first',
3052
							'failed_connect_api',
3053
						) );
3054
3055
						if ( $this->_anonymous_mode ) {
3056
							// Simulate anonymous mode.
3057
							$this->_is_anonymous = true;
3058
						}
3059
					}
3060
				}
3061
3062
				// Check if Freemius is on for the current plugin.
3063
				// This MUST be executed after all the plugin variables has been loaded.
3064
				if ( ! $this->is_on() ) {
3065
					return;
3066
				}
3067
			}
3068
3069
			if ( $this->has_api_connectivity() ) {
3070
				if ( self::is_cron() ) {
3071
					$this->hook_callback_to_sync_cron();
3072
				} else if ( $this->is_user_in_admin() ) {
3073
					/**
3074
					 * Schedule daily data sync cron if:
3075
					 *
3076
					 *  1. User opted-in (for tracking).
3077
					 *  2. If skipped, but later upgraded (opted-in via upgrade).
3078
					 *
3079
					 * @author Vova Feldman (@svovaf)
3080
					 * @since  1.1.7.3
3081
					 *
3082
					 */
3083
					if ( $this->is_registered() ) {
3084
						if ( ! $this->is_sync_cron_on() && $this->is_tracking_allowed() ) {
3085
							$this->schedule_sync_cron();
3086
						}
3087
					}
3088
3089
					/**
3090
					 * Check if requested for manual blocking background sync.
3091
					 */
3092
					if ( fs_request_has( 'background_sync' ) ) {
3093
						$this->run_manual_sync();
3094
					}
3095
				}
3096
			}
3097
3098
			if ( $this->is_registered() ) {
3099
				$this->hook_callback_to_install_sync();
3100
			}
3101
3102
			if ( $this->is_addon() ) {
3103
				if ( $this->is_parent_plugin_installed() ) {
3104
					// Link to parent FS.
3105
					$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...
3106
3107
					// Get parent plugin reference.
3108
					$this->_parent_plugin = $this->_parent->get_plugin();
3109
				}
3110
			}
3111
3112
			if ( $this->is_user_in_admin() ) {
3113
				if ( self::is_plugins_page() && $this->is_plugin() ) {
3114
					$this->hook_plugin_action_links();
3115
				}
3116
3117
				if ( $this->is_addon() ) {
3118
					if ( ! $this->is_parent_plugin_installed() ) {
3119
						$parent_name = $this->get_option( $plugin_info, 'parent_name', null );
3120
3121
						if ( isset( $plugin_info['parent'] ) ) {
3122
							$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...
3123
						}
3124
3125
						$this->_admin_notices->add(
3126
							( ! empty( $parent_name ) ?
3127
								sprintf( $this->get_text_x_inline( '%s cannot run without %s.', 'addonX cannot run without pluginY', 'addon-x-cannot-run-without-y' ), $this->get_plugin_name(), $parent_name ) :
3128
								sprintf( $this->get_text_x_inline( '%s cannot run without the plugin.', 'addonX cannot run...', 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() )
3129
							),
3130
							$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
3131
							'error'
3132
						);
3133
3134
						return;
3135
					} else {
3136
						if ( $this->_parent->is_registered() && ! $this->is_registered() ) {
3137
							// If parent plugin activated, automatically install add-on for the user.
3138
							$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...
3139
						} else if ( ! $this->_parent->is_registered() && $this->is_registered() ) {
3140
							// If add-on activated and parent not, automatically install parent for the user.
3141
							$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...
3142
						}
3143
3144
						// @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic.
3145
						if ( $this->is_premium() ) {
3146
							// Remove add-on download admin-notice.
3147
							$this->_parent->_admin_notices->remove_sticky( array(
3148
								'addon_plan_upgraded_' . $this->_slug,
3149
								'no_addon_license_' . $this->_slug,
3150
							) );
3151
						}
3152
3153
//						$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...
3154
					}
3155
				} else {
3156
					if ( $this->has_addons() &&
3157
					     'plugin-information' === fs_request_get( 'tab', false ) &&
3158
					     $this->get_id() == fs_request_get( 'parent_plugin_id', false )
3159
					) {
3160
						require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php';
3161
3162
						new FS_Plugin_Info_Dialog( $this );
3163
					}
3164
				}
3165
3166
				add_action( 'admin_init', array( &$this, '_admin_init_action' ) );
3167
3168
//				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...
3169
//				     $this->is_anonymous() ||
3170
//				     $this->is_pending_activation()
3171
//				) {
3172
//					$this->_init_admin();
3173
//				}
3174
			}
3175
3176
			/**
3177
			 * Should be called outside `$this->is_user_in_admin()` scope
3178
			 * because the updater has some logic that needs to be executed
3179
			 * during AJAX calls.
3180
			 *
3181
			 * Currently we need to hook to the `http_request_host_is_external` filter.
3182
			 * In the future, there might be additional logic added.
3183
			 *
3184
			 * @author Vova Feldman
3185
			 * @since  1.2.1.6
3186
			 */
3187
			if ( $this->is_premium() && $this->has_release_on_freemius() ) {
3188
				new FS_Plugin_Updater( $this );
3189
			}
3190
3191
			$this->do_action( 'initiated' );
3192
3193
			if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) {
3194
				if ( isset( $this->_storage->prev_is_premium ) ) {
3195
					$this->apply_filters(
3196
						'after_code_type_change',
3197
						// New code type.
3198
						$this->_plugin->is_premium
3199
					);
3200
				} else {
3201
					// Set for code type for the first time.
3202
					$this->_storage->prev_is_premium = $this->_plugin->is_premium;
3203
				}
3204
			}
3205
3206
			if ( ! $this->is_addon() ) {
3207
				if ( $this->is_registered() ) {
3208
					// Fix for upgrade from versions < 1.0.9.
3209
					if ( ! isset( $this->_storage->activation_timestamp ) ) {
3210
						$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
3211
					}
3212
3213
					$this->do_action( 'after_init_plugin_registered' );
3214
				} else if ( $this->is_anonymous() ) {
3215
					$this->do_action( 'after_init_plugin_anonymous' );
3216
				} else if ( $this->is_pending_activation() ) {
3217
					$this->do_action( 'after_init_plugin_pending_activations' );
3218
				}
3219
			} else {
3220
				if ( $this->is_registered() ) {
3221
					$this->do_action( 'after_init_addon_registered' );
3222
				} else if ( $this->is_anonymous() ) {
3223
					$this->do_action( 'after_init_addon_anonymous' );
3224
				} else if ( $this->is_pending_activation() ) {
3225
					$this->do_action( 'after_init_addon_pending_activations' );
3226
				}
3227
			}
3228
		}
3229
3230
		/**
3231
		 * @author Leo Fajardo (@leorw)
3232
		 *
3233
		 * @since  1.2.1.5
3234
		 */
3235
		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...
3236
			$this->_logger->entrance();
3237
3238
			$this->check_ajax_referer( 'stop_tracking' );
3239
3240
			$result = $this->stop_tracking();
3241
3242
			if ( true === $result ) {
3243
				self::shoot_ajax_success();
3244
			}
3245
3246
			$this->_logger->api_error( $result );
3247
3248
			self::shoot_ajax_failure(
3249
				sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) .
3250
				( $this->is_api_error( $result ) && isset( $result->error ) ?
3251
					$result->error->message :
3252
					var_export( $result, true ) )
3253
			);
3254
		}
3255
3256
		/**
3257
		 * @author Leo Fajardo (@leorw)
3258
		 * @since  1.2.1.5
3259
		 */
3260
		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...
3261
			$this->_logger->entrance();
3262
3263
			$this->check_ajax_referer( 'allow_tracking' );
3264
3265
			$result = $this->allow_tracking();
3266
3267
			if ( true === $result ) {
3268
				self::shoot_ajax_success();
3269
			}
3270
3271
			$this->_logger->api_error( $result );
3272
3273
			self::shoot_ajax_failure(
3274
				sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) .
3275
				( $this->is_api_error( $result ) && isset( $result->error ) ?
3276
					$result->error->message :
3277
					var_export( $result, true ) )
3278
			);
3279
		}
3280
3281
		/**
3282
		 * Opt-out from usage tracking.
3283
		 *
3284
		 * Note: This will not delete the account information but will stop all tracking.
3285
		 *
3286
		 * Returns:
3287
		 *  1. FALSE  - If the user never opted-in.
3288
		 *  2. TRUE   - If successfully opted-out.
3289
		 *  3. object - API result on failure.
3290
		 *
3291
		 * @author Leo Fajardo (@leorw)
3292
		 * @since  1.2.1.5
3293
		 *
3294
		 * @return bool|object
3295
		 */
3296
		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...
3297
			$this->_logger->entrance();
3298
3299
			if ( ! $this->is_registered() ) {
3300
				// User never opted-in.
3301
				return false;
3302
			}
3303
3304
			if ( $this->is_tracking_prohibited() ) {
3305
				// Already disconnected.
3306
				return true;
3307
			}
3308
3309
			// Send update to FS.
3310
			$result = $this->get_api_site_scope()->call( '/?fields=is_disconnected', 'put', array(
3311
				'is_disconnected' => true
3312
			) );
3313
3314
			if ( ! $this->is_api_result_entity( $result ) ||
3315
			     ! isset( $result->is_disconnected ) ||
3316
			     ! $result->is_disconnected
3317
			) {
3318
				$this->_logger->api_error( $result );
3319
3320
				return $result;
3321
			}
3322
3323
			$this->_site->is_disconnected = $result->is_disconnected;
3324
			$this->_store_site();
3325
3326
			$this->clear_sync_cron();
3327
3328
			// Successfully disconnected.
3329
			return true;
3330
		}
3331
3332
		/**
3333
		 * Opt-in back into usage tracking.
3334
		 *
3335
		 * Note: This will only work if the user opted-in previously.
3336
		 *
3337
		 * Returns:
3338
		 *  1. FALSE  - If the user never opted-in.
3339
		 *  2. TRUE   - If successfully opted-in back to usage tracking.
3340
		 *  3. object - API result on failure.
3341
		 *
3342
		 * @author Leo Fajardo (@leorw)
3343
		 * @since  1.2.1.5
3344
		 *
3345
		 * @return bool|object
3346
		 */
3347
		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...
3348
			$this->_logger->entrance();
3349
3350
			if ( ! $this->is_registered() ) {
3351
				// User never opted-in.
3352
				return false;
3353
			}
3354
3355
			if ( $this->is_tracking_allowed() ) {
3356
				// Tracking already allowed.
3357
				return true;
3358
			}
3359
3360
			$result = $this->get_api_site_scope()->call( '/?is_disconnected', 'put', array(
3361
				'is_disconnected' => false
3362
			) );
3363
3364
			if ( ! $this->is_api_result_entity( $result ) ||
3365
			     ! isset( $result->is_disconnected ) ||
3366
			     $result->is_disconnected
3367
			) {
3368
				$this->_logger->api_error( $result );
3369
3370
				return $result;
3371
			}
3372
3373
			$this->_site->is_disconnected = $result->is_disconnected;
3374
			$this->_store_site();
3375
3376
			$this->schedule_sync_cron();
3377
3378
			// Successfully reconnected.
3379
			return true;
3380
		}
3381
3382
		/**
3383
		 * If user opted-in and later disabled usage-tracking,
3384
		 * re-allow tracking for licensing and updates.
3385
		 *
3386
		 * @author Leo Fajardo (@leorw)
3387
		 *
3388
		 * @since  1.2.1.5
3389
		 */
3390
		private function reconnect_locally() {
3391
			$this->_logger->entrance();
3392
3393
			if ( $this->is_tracking_prohibited() &&
3394
			     $this->is_registered()
3395
			) {
3396
				$this->_site->is_disconnected = false;
3397
				$this->_store_site();
3398
			}
3399
		}
3400
3401
		/**
3402
		 * Parse plugin's settings (as defined by the plugin dev).
3403
		 *
3404
		 * @author Vova Feldman (@svovaf)
3405
		 * @since  1.1.7.3
3406
		 *
3407
		 * @param array $plugin_info
3408
		 *
3409
		 * @throws \Freemius_Exception
3410
		 */
3411
		private function parse_settings( &$plugin_info ) {
3412
			$this->_logger->entrance();
3413
3414
			$id          = $this->get_numeric_option( $plugin_info, 'id', false );
3415
			$public_key  = $this->get_option( $plugin_info, 'public_key', false );
3416
			$secret_key  = $this->get_option( $plugin_info, 'secret_key', null );
3417
			$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...
3418
			$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...
3419
3420
			/**
3421
			 * @author Vova Feldman (@svovaf)
3422
			 * @since  1.1.9 Try to pull secret key from external config.
3423
			 */
3424
			if ( is_null( $secret_key ) && defined( "WP_FS__{$this->_slug}_SECRET_KEY" ) ) {
3425
				$secret_key = constant( "WP_FS__{$this->_slug}_SECRET_KEY" );
3426
			}
3427
3428
			if ( isset( $plugin_info['parent'] ) ) {
3429
				$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...
3430
//				$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...
3431
//				$parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null );
3432
//				$parent_name = $this->get_option( $plugin_info['parent'], 'name', null );
3433
			}
3434
3435
			if ( false === $id ) {
3436
				throw new Freemius_Exception( array(
3437
					'error' => array(
3438
						'type'    => 'ParameterNotSet',
3439
						'message' => 'Plugin id parameter is not set.',
3440
						'code'    => 'plugin_id_not_set',
3441
						'http'    => 500,
3442
					)
3443
				) );
3444
			}
3445
			if ( false === $public_key ) {
3446
				throw new Freemius_Exception( array(
3447
					'error' => array(
3448
						'type'    => 'ParameterNotSet',
3449
						'message' => 'Plugin public_key parameter is not set.',
3450
						'code'    => 'plugin_public_key_not_set',
3451
						'http'    => 500,
3452
					)
3453
				) );
3454
			}
3455
3456
			$plugin = ( $this->_plugin instanceof FS_Plugin ) ?
3457
				$this->_plugin :
3458
				new FS_Plugin();
3459
3460
			$plugin->update( array(
3461
				'id'                   => $id,
3462
				'type'                 => $this->get_option( $plugin_info, 'type', $this->_module_type),
3463
				'public_key'           => $public_key,
3464
				'slug'                 => $this->_slug,
3465
				'parent_plugin_id'     => $parent_id,
3466
				'version'              => $this->get_plugin_version(),
3467
				'title'                => $this->get_plugin_name(),
3468
				'file'                 => $this->_plugin_basename,
3469
				'is_premium'           => $this->get_bool_option( $plugin_info, 'is_premium', true ),
3470
				'is_live'              => $this->get_bool_option( $plugin_info, 'is_live', true ),
3471
				'affiliate_moderation' => $this->get_option( $plugin_info, 'has_affiliation' ),
3472
			) );
3473
3474
			if ( $plugin->is_updated() ) {
3475
				// Update plugin details.
3476
				$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...
3477
			}
3478
			// Set the secret key after storing the plugin, we don't want to store the key in the storage.
3479
			$this->_plugin->secret_key = $secret_key;
3480
3481
			if ( ! isset( $plugin_info['menu'] ) ) {
3482
				$plugin_info['menu'] = array();
3483
3484
				if ( ! empty( $this->_storage->sdk_last_version ) &&
3485
				     version_compare( $this->_storage->sdk_last_version, '1.1.2', '<=' )
3486
				) {
3487
					// Backward compatibility to 1.1.2
3488
					$plugin_info['menu']['slug'] = isset( $plugin_info['menu_slug'] ) ?
3489
						$plugin_info['menu_slug'] :
3490
						$this->_slug;
3491
				}
3492
			}
3493
3494
			$this->_menu = FS_Admin_Menu_Manager::instance(
3495
				$this->_module_id,
3496
				$this->_module_type,
3497
				$this->get_unique_affix()
3498
			);
3499
3500
			$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...
3501
3502
			$this->_has_addons          = $this->get_bool_option( $plugin_info, 'has_addons', false );
3503
			$this->_has_paid_plans      = $this->get_bool_option( $plugin_info, 'has_paid_plans', true );
3504
			$this->_has_premium_version = $this->get_bool_option( $plugin_info, 'has_premium_version', $this->_has_paid_plans );
3505
			$this->_ignore_pending_mode = $this->get_bool_option( $plugin_info, 'ignore_pending_mode', false );
3506
			$this->_is_org_compliant    = $this->get_bool_option( $plugin_info, 'is_org_compliant', true );
3507
			$this->_is_premium_only     = $this->get_bool_option( $plugin_info, 'is_premium_only', false );
3508
			if ( $this->_is_premium_only ) {
3509
				// If premium only plugin, disable anonymous mode.
3510
				$this->_enable_anonymous = false;
3511
				$this->_anonymous_mode   = false;
3512
			} else {
3513
				$this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true );
3514
				$this->_anonymous_mode   = $this->get_bool_option( $plugin_info, 'anonymous_mode', false );
3515
			}
3516
			$this->_permissions = $this->get_option( $plugin_info, 'permissions', array() );
3517
3518
			if ( ! empty( $plugin_info['trial'] ) ) {
3519
				$this->_trial_days = $this->get_numeric_option(
3520
					$plugin_info['trial'],
3521
					'days',
3522
					// Default to 0 - trial without days specification.
3523
					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...
3524
				);
3525
3526
				$this->_is_trial_require_payment = $this->get_bool_option( $plugin_info['trial'], 'is_require_payment', false );
3527
			}
3528
		}
3529
3530
		/**
3531
		 * @param string[] $options
3532
		 * @param string   $key
3533
		 * @param mixed    $default
3534
		 *
3535
		 * @return bool
3536
		 */
3537
		private function get_option( &$options, $key, $default = false ) {
3538
			return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
3539
		}
3540
3541
		private function get_bool_option( &$options, $key, $default = false ) {
3542
			return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
3543
		}
3544
3545
		private function get_numeric_option( &$options, $key, $default = false ) {
3546
			return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default;
3547
		}
3548
3549
		/**
3550
		 * Gate keeper.
3551
		 *
3552
		 * @author Vova Feldman (@svovaf)
3553
		 * @since  1.1.7.3
3554
		 *
3555
		 * @return bool
3556
		 */
3557
		private function should_stop_execution() {
3558
			if ( empty( $this->_storage->was_plugin_loaded ) ) {
3559
				/**
3560
				 * Don't execute Freemius until plugin was fully loaded at least once,
3561
				 * to give the opportunity for the activation hook to run before pinging
3562
				 * the API for connectivity test. This logic is relevant for the
3563
				 * identification of new plugin install vs. plugin update.
3564
				 *
3565
				 * @author Vova Feldman (@svovaf)
3566
				 * @since  1.1.9
3567
				 */
3568
				return true;
3569
			}
3570
3571
			if ( $this->is_activation_mode() ) {
3572
				if ( ! is_admin() ) {
3573
					/**
3574
					 * If in activation mode, don't execute Freemius outside of the
3575
					 * admin dashboard.
3576
					 *
3577
					 * @author Vova Feldman (@svovaf)
3578
					 * @since  1.1.7.3
3579
					 */
3580
					return true;
3581
				}
3582
3583
				if ( ! WP_FS__IS_HTTP_REQUEST ) {
3584
					/**
3585
					 * If in activation and executed without HTTP context (e.g. CLI, Cronjob),
3586
					 * then don't start Freemius.
3587
					 *
3588
					 * @author Vova Feldman (@svovaf)
3589
					 * @since  1.1.6.3
3590
					 *
3591
					 * @link   https://wordpress.org/support/topic/errors-in-the-freemius-class-when-running-in-wordpress-in-cli
3592
					 */
3593
					return true;
3594
				}
3595
3596
				if ( self::is_cron() ) {
3597
					/**
3598
					 * If in activation mode, don't execute Freemius during wp crons
3599
					 * (wp crons have HTTP context - called as HTTP request).
3600
					 *
3601
					 * @author Vova Feldman (@svovaf)
3602
					 * @since  1.1.7.3
3603
					 */
3604
					return true;
3605
				}
3606
3607
				if ( self::is_ajax() &&
3608
				     ! $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) &&
3609
				     ! $this->_admin_notices->has_sticky( 'failed_connect_api' )
3610
				) {
3611
					/**
3612
					 * During activation, if running in AJAX mode, unless there's a sticky
3613
					 * connectivity issue notice, don't run Freemius.
3614
					 *
3615
					 * @author Vova Feldman (@svovaf)
3616
					 * @since  1.1.7.3
3617
					 */
3618
					return true;
3619
				}
3620
			}
3621
3622
			return false;
3623
		}
3624
3625
		/**
3626
		 * Triggered after code type has changed.
3627
		 *
3628
		 * @author Vova Feldman (@svovaf)
3629
		 * @since  1.1.9.1
3630
		 */
3631
		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...
3632
			$this->_logger->entrance();
3633
3634
			if ( $this->is_theme() ) {
3635
				// Expire the cache of the previous tabs since the theme may
3636
				// have setting updates after code type has changed.
3637
				$this->_cache->expire( 'tabs' );
3638
				$this->_cache->expire( 'tabs_stylesheets' );
3639
			}
3640
3641
			if ( $this->is_registered() ) {
3642
				if ( ! $this->is_addon() ) {
3643
					add_action(
3644
						is_admin() ? 'admin_init' : 'init',
3645
						array( &$this, '_plugin_code_type_changed' )
3646
					);
3647
				}
3648
3649
				if ( $this->is_premium() ) {
3650
					// Purge cached payments after switching to the premium version.
3651
					// @todo This logic doesn't handle purging the cache for serviceware module upgrade.
3652
					$this->get_api_user_scope()->purge_cache( "/plugins/{$this->_module_id}/payments.json?include_addons=true" );
3653
				}
3654
			}
3655
		}
3656
3657
		/**
3658
		 * Handles plugin's code type change (free <--> premium).
3659
		 *
3660
		 * @author Vova Feldman (@svovaf)
3661
		 * @since  1.0.9
3662
		 */
3663
		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...
3664
			$this->_logger->entrance();
3665
3666
			if ( $this->is_premium() ) {
3667
				$this->reconnect_locally();
3668
3669
				// Activated premium code.
3670
				$this->do_action( 'after_premium_version_activation' );
3671
3672
				// Remove all sticky messages related to download of the premium version.
3673
				$this->_admin_notices->remove_sticky( array(
3674
					'trial_started',
3675
					'plan_upgraded',
3676
					'plan_changed',
3677
					'license_activated',
3678
				) );
3679
3680
				if ( ! $this->is_only_premium() ) {
3681
                    $this->_admin_notices->add_sticky(
3682
                        sprintf( $this->get_text_inline( 'Premium %s version was successfully activated.', 'premium-activated-message' ), $this->_module_type ),
3683
                        'premium_activated',
3684
                        $this->get_text_x_inline( 'W00t',
3685
	                        'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!'
3686
                    );
3687
                }
3688
			} else {
3689
				// Remove sticky message related to premium code activation.
3690
				$this->_admin_notices->remove_sticky( 'premium_activated' );
3691
3692
				// Activated free code (after had the premium before).
3693
				$this->do_action( 'after_free_version_reactivation' );
3694
3695
				if ( $this->is_paying() && ! $this->is_premium() ) {
3696
					$this->_admin_notices->add_sticky(
3697
						sprintf(
3698
							/* translators: %s: License type (e.g. you have a professional license) */
3699
							$this->get_text_inline( 'You have a %s license.', 'you-have-x-license' ),
3700
							$this->_site->plan->title
3701
						) . $this->get_complete_upgrade_instructions(),
3702
						'plan_upgraded',
3703
						$this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!'
3704
					);
3705
				}
3706
			}
3707
3708
			// Schedule code type changes event.
3709
			$this->schedule_install_sync();
3710
3711
			/**
3712
			 * Unregister the uninstall hook for the other version of the plugin (with different code type) to avoid
3713
			 * triggering a fatal error when uninstalling that plugin. For example, after deactivating the "free" version
3714
			 * of a specific plugin, its uninstall hook should be unregistered after the "premium" version has been
3715
			 * activated. If we don't do that, a fatal error will occur when we try to uninstall the "free" version since
3716
			 * the main file of the "free" version will be loaded first before calling the hooked callback. Since the
3717
			 * free and premium versions are almost identical (same class or have same functions), a fatal error like
3718
			 * "Cannot redeclare class MyClass" or "Cannot redeclare my_function()" will occur.
3719
			 */
3720
			$this->unregister_uninstall_hook();
3721
3722
			$this->clear_module_main_file_cache();
3723
3724
			// Update is_premium of latest version.
3725
			$this->_storage->prev_is_premium = $this->_plugin->is_premium;
3726
		}
3727
3728
		#endregion
3729
3730
		#----------------------------------------------------------------------------------
3731
		#region Add-ons
3732
		#----------------------------------------------------------------------------------
3733
3734
		/**
3735
		 * Check if add-on installed and activated on site.
3736
		 *
3737
		 * @author Vova Feldman (@svovaf)
3738
		 * @since  1.0.6
3739
		 *
3740
		 * @param string|number $id_or_slug
3741
		 * @param bool|null     $is_premium Since 1.2.1.7 can check for specified add-on version.
3742
		 *
3743
		 * @return bool
3744
		 */
3745
		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...
3746
			$this->_logger->entrance();
3747
3748
			$addon_id     = self::get_module_id( $id_or_slug );
3749
			$is_activated = self::has_instance( $addon_id );
3750
3751
			if ( ! $is_activated ) {
3752
				return false;
3753
			}
3754
3755
			if ( is_bool( $is_premium ) ) {
3756
				// Check if the specified code version is activate.
3757
				$addon        = $this->get_addon_instance( $addon_id );
3758
				$is_activated = ( $is_premium === $addon->is_premium() );
3759
			}
3760
3761
			return $is_activated;
3762
		}
3763
3764
		/**
3765
		 * Check if add-on was connected to install
3766
		 *
3767
		 * @author Vova Feldman (@svovaf)
3768
		 * @since  1.1.7
3769
		 *
3770
		 * @param  string|number $id_or_slug
3771
		 *
3772
		 * @return bool
3773
		 */
3774
		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...
3775
			$this->_logger->entrance();
3776
3777
			$sites = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN );
3778
3779
			$addon_id = self::get_module_id( $id_or_slug );
3780
			$addon    = $this->get_addon( $addon_id );
3781
			$slug     = $addon->slug;
3782
			if ( ! isset( $sites[ $slug ] ) ) {
3783
				return false;
3784
			}
3785
3786
			$site = $sites[ $slug ];
3787
3788
			$plugin = FS_Plugin_Manager::instance( $addon_id )->get();
3789
3790
			if ( $plugin->parent_plugin_id != $this->_plugin->id ) {
3791
				// The given slug do NOT belong to any of the plugin's add-ons.
3792
				return false;
3793
			}
3794
3795
			return ( is_object( $site ) &&
3796
			         is_numeric( $site->id ) &&
3797
			         is_numeric( $site->user_id ) &&
3798
			         is_object( $site->plan )
3799
			);
3800
		}
3801
3802
		/**
3803
		 * Determines if add-on installed.
3804
		 *
3805
		 * NOTE: This is a heuristic and only works if the folder/file named as the slug.
3806
		 *
3807
		 * @author Vova Feldman (@svovaf)
3808
		 * @since  1.0.6
3809
		 *
3810
		 * @param  string|number $id_or_slug
3811
		 *
3812
		 * @return bool
3813
		 */
3814
		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...
3815
			$this->_logger->entrance();
3816
3817
			$addon_id = self::get_module_id( $id_or_slug );
3818
3819
			return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $addon_id ) ) );
3820
		}
3821
3822
		/**
3823
		 * Get add-on basename.
3824
		 *
3825
		 * @author Vova Feldman (@svovaf)
3826
		 * @since  1.0.6
3827
		 *
3828
		 * @param  string|number $id_or_slug
3829
		 *
3830
		 * @return string
3831
		 */
3832
		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...
3833
			$addon_id = self::get_module_id( $id_or_slug );
3834
3835
			if ( $this->is_addon_activated( $addon_id ) ) {
3836
				return self::instance( $addon_id )->get_plugin_basename();
3837
			}
3838
3839
			$addon            = $this->get_addon( $addon_id );
3840
			$premium_basename = "{$addon->slug}-premium/{$addon->slug}.php";
3841
3842
			if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_basename ) ) ) {
3843
				return $premium_basename;
3844
			}
3845
3846
			$all_plugins = $this->get_all_plugins();
3847
3848
			foreach ( $all_plugins as $basename => &$data ) {
3849
				if ( $addon->slug === $data['slug'] ||
3850
                    $addon->slug . '-premium' === $data['slug']
3851
				) {
3852
					return $basename;
3853
				}
3854
			}
3855
3856
			$free_basename = "{$addon->slug}/{$addon->slug}.php";
3857
3858
			return $free_basename;
3859
		}
3860
3861
		/**
3862
		 * Get installed add-ons instances.
3863
		 *
3864
		 * @author Vova Feldman (@svovaf)
3865
		 * @since  1.0.6
3866
		 *
3867
		 * @return Freemius[]
3868
		 */
3869
		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...
3870
			$installed_addons = array();
3871
			foreach ( self::$_instances as $instance ) {
3872
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
3873
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
3874
						$installed_addons[] = $instance;
3875
					}
3876
				}
3877
			}
3878
3879
			return $installed_addons;
3880
		}
3881
3882
		/**
3883
		 * Check if any add-ons of the plugin are installed.
3884
		 *
3885
		 * @author Leo Fajardo (@leorw)
3886
		 * @since  1.1.1
3887
		 *
3888
		 * @return bool
3889
		 */
3890
		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...
3891
			if ( ! $this->has_addons() ) {
3892
				return false;
3893
			}
3894
3895
			foreach ( self::$_instances as $instance ) {
3896
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
3897
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
3898
						return true;
3899
					}
3900
				}
3901
			}
3902
3903
			return false;
3904
		}
3905
3906
		/**
3907
		 * Tell Freemius that the current plugin is an add-on.
3908
		 *
3909
		 * @author Vova Feldman (@svovaf)
3910
		 * @since  1.0.6
3911
		 *
3912
		 * @param number $parent_plugin_id The parent plugin ID
3913
		 */
3914
		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...
3915
			$this->_plugin->parent_plugin_id = $parent_plugin_id;
3916
		}
3917
3918
		/**
3919
		 * @author Vova Feldman (@svovaf)
3920
		 * @since  1.0.6
3921
		 *
3922
		 * @return bool
3923
		 */
3924
		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...
3925
			return isset( $this->_plugin->parent_plugin_id ) && is_numeric( $this->_plugin->parent_plugin_id );
3926
		}
3927
3928
		/**
3929
		 * Deactivate add-on if it's premium only and the user does't have a valid license.
3930
		 *
3931
		 * @param bool $is_after_trial_cancel
3932
		 *
3933
		 * @return bool If add-on was deactivated.
3934
		 */
3935
		private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) {
3936
			if ( ! $this->has_free_plan() &&
3937
			     ! $this->has_features_enabled_license() &&
3938
			     ! $this->_has_premium_license()
3939
			) {
3940
				if ( $this->is_registered() ) {				
3941
					// IF wrapper is turned off because activation_timestamp is currently only stored for plugins (not addons).
3942
	//                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...
3943
	//                    (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...
3944
	//                ) {
3945
					/**
3946
					 * @todo When it's first fail, there's no reason to try and re-sync because the licenses were just synced after initial activation.
3947
					 *
3948
					 * Retry syncing the user add-on licenses.
3949
					 */
3950
					// Sync licenses.
3951
					$this->_sync_licenses();
3952
	//                }
3953
3954
					// Try to activate premium license.
3955
					$this->_activate_license( true );
3956
				}
3957
3958
				if ( ! $this->has_free_plan() &&
3959
				     ! $this->has_features_enabled_license() &&
3960
				     ! $this->_has_premium_license()
3961
				) {
3962
					// @todo Check if deactivate plugins also call the deactivation hook.
3963
3964
					$this->_parent->_admin_notices->add_sticky(
3965
						sprintf(
3966
							($is_after_trial_cancel ?
3967
								$this->_parent->get_text_inline(
3968
									'%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.',
3969
									'addon-trial-cancelled-message'
3970
								) :
3971
								$this->_parent->get_text_inline(
3972
									'%s is a premium only add-on. You have to purchase a license first before activating the plugin.',
3973
									'addon-no-license-message'
3974
								)
3975
							),
3976
							'<b>' . $this->_plugin->title . '</b>'
3977
						) . ' ' . sprintf(
3978
							'<a href="%s" aria-label="%s" class="button button-primary" style="margin-left: 10px; vertical-align: middle;">%s &nbsp;&#10140;</a>',
3979
							$this->_parent->addon_url( $this->_slug ),
3980
							esc_attr( sprintf( $this->_parent->get_text_inline( 'More information about %s', 'more-information-about-x' ), $this->_plugin->title ) ),
3981
							$this->_parent->get_text_inline( 'Purchase License', 'purchase-license' )
3982
						),
3983
						'no_addon_license_' . $this->_slug,
3984
						( $is_after_trial_cancel ? '' : $this->_parent->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' ),
3985
						( $is_after_trial_cancel ? 'success' : 'error' )
3986
					);
3987
3988
					deactivate_plugins( array( $this->_plugin_basename ), true );
3989
3990
					return true;
3991
				}
3992
			}
3993
3994
			return false;
3995
		}
3996
3997
		#endregion
3998
3999
		#----------------------------------------------------------------------------------
4000
		#region Sandbox
4001
		#----------------------------------------------------------------------------------
4002
4003
		/**
4004
		 * Set Freemius into sandbox mode for debugging.
4005
		 *
4006
		 * @author Vova Feldman (@svovaf)
4007
		 * @since  1.0.4
4008
		 *
4009
		 * @param string $secret_key
4010
		 */
4011
		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...
4012
			$this->_plugin->secret_key = $secret_key;
4013
4014
			// Update plugin details.
4015
			FS_Plugin_Manager::instance( $this->_module_id )->update( $this->_plugin, true );
4016
		}
4017
4018
		/**
4019
		 * Check if running payments in sandbox mode.
4020
		 *
4021
		 * @author Vova Feldman (@svovaf)
4022
		 * @since  1.0.4
4023
		 *
4024
		 * @return bool
4025
		 */
4026
		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...
4027
			return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key );
4028
		}
4029
4030
		#endregion
4031
4032
		/**
4033
		 * Check if running test vs. live plugin.
4034
		 *
4035
		 * @author Vova Feldman (@svovaf)
4036
		 * @since  1.0.5
4037
		 *
4038
		 * @return bool
4039
		 */
4040
		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...
4041
			return $this->_plugin->is_live;
4042
		}
4043
4044
		/**
4045
		 * Check if the user skipped connecting the account with Freemius.
4046
		 *
4047
		 * @author Vova Feldman (@svovaf)
4048
		 * @since  1.0.7
4049
		 *
4050
		 * @return bool
4051
		 */
4052
		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...
4053
			if ( ! isset( $this->_is_anonymous ) ) {
4054
				if ( ! isset( $this->_storage->is_anonymous ) ) {
4055
					// Not skipped.
4056
					$this->_is_anonymous = false;
4057
				} else if ( is_bool( $this->_storage->is_anonymous ) ) {
4058
					// For back compatibility, since the variable was boolean before.
4059
					$this->_is_anonymous = $this->_storage->is_anonymous;
4060
4061
					// Upgrade stored data format to 1.1.3 format.
4062
					$this->set_anonymous_mode( $this->_storage->is_anonymous );
4063
				} else {
4064
					// Version 1.1.3 and later.
4065
					$this->_is_anonymous = $this->_storage->is_anonymous['is'];
4066
				}
4067
			}
4068
4069
			return $this->_is_anonymous;
4070
		}
4071
4072
		/**
4073
		 * Check if user connected his account and install pending email activation.
4074
		 *
4075
		 * @author Vova Feldman (@svovaf)
4076
		 * @since  1.0.7
4077
		 *
4078
		 * @return bool
4079
		 */
4080
		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...
4081
			return $this->_storage->get( 'is_pending_activation', false );
4082
		}
4083
4084
		/**
4085
		 * Check if plugin must be WordPress.org compliant.
4086
		 *
4087
		 * @since 1.0.7
4088
		 *
4089
		 * @return bool
4090
		 */
4091
		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...
4092
			return $this->_is_org_compliant;
4093
		}
4094
4095
		#----------------------------------------------------------------------------------
4096
		#region Daily Sync Cron
4097
		#----------------------------------------------------------------------------------
4098
4099
		/**
4100
		 * @author Vova Feldman (@svovaf)
4101
		 * @since  1.1.7.3
4102
		 */
4103
		private function run_manual_sync() {
4104
			self::require_pluggable_essentials();
4105
4106
			if ( ! $this->is_user_admin() ) {
4107
				return;
4108
			}
4109
4110
			// Run manual sync.
4111
			$this->_sync_cron();
4112
4113
			// Reschedule next cron to run 24 hours from now (performance optimization).
4114
			$this->clear_sync_cron();
4115
4116
			$this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false );
4117
		}
4118
4119
		/**
4120
		 * Data sync cron job. Replaces the background sync non blocking HTTP request
4121
		 * that doesn't halt page loading.
4122
		 *
4123
		 * @author Vova Feldman (@svovaf)
4124
		 * @since  1.1.7.3
4125
		 */
4126
		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...
4127
			$this->_logger->entrance();
4128
4129
			// Store the last time data sync was executed.
4130
			$this->_storage->sync_timestamp = time();
4131
4132
			// Check if API is temporary down.
4133
			if ( FS_Api::is_temporary_down() ) {
4134
				return;
4135
			}
4136
4137
			// @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours.
4138
4139
			if ( $this->is_registered() ) {
4140
				if ( $this->has_paid_plan() ) {
4141
					// Initiate background plan sync.
4142
					$this->_sync_license( true );
4143
4144
					if ( $this->is_paying() ) {
4145
						// Check for premium plugin updates.
4146
						$this->check_updates( true );
4147
					}
4148
				} else {
4149
					// Sync install (only if something changed locally).
4150
					$this->sync_install();
4151
				}
4152
			}
4153
4154
			$this->do_action( 'after_sync_cron' );
4155
		}
4156
4157
		/**
4158
		 * Check if sync was executed in the last $period of seconds.
4159
		 *
4160
		 * @author Vova Feldman (@svovaf)
4161
		 * @since  1.1.7.3
4162
		 *
4163
		 * @param int $period In seconds
4164
		 *
4165
		 * @return bool
4166
		 */
4167
		private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) {
4168
			if ( ! isset( $this->_storage->sync_timestamp ) ) {
4169
				return false;
4170
			}
4171
4172
			return ( $this->_storage->sync_timestamp > ( WP_FS__SCRIPT_START_TIME - $period ) );
4173
		}
4174
4175
		/**
4176
		 * @author Vova Feldman (@svovaf)
4177
		 * @since  1.1.7.3
4178
		 *
4179
		 * @return bool
4180
		 */
4181
		private function is_sync_cron_on() {
4182
			/**
4183
			 * @var object $sync_cron_data
4184
			 */
4185
			$sync_cron_data = $this->_storage->get( 'sync_cron', null );
4186
4187
			return ( ! is_null( $sync_cron_data ) && true === $sync_cron_data->on );
4188
		}
4189
4190
		/**
4191
		 * @author Vova Feldman (@svovaf)
4192
		 * @since  1.1.7.3
4193
		 *
4194
		 * @param int  $start_at        Defaults to now.
4195
		 * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise,
4196
		 *                              schedule job to start right away.
4197
		 */
4198
		private function schedule_sync_cron( $start_at = WP_FS__SCRIPT_START_TIME, $randomize_start = true ) {
4199
			$this->_logger->entrance();
4200
4201
			if ( $randomize_start ) {
4202
				// Schedule first sync with a random 12 hour time range from now.
4203
				$start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) );
4204
			}
4205
4206
			// Schedule daily WP cron.
4207
			wp_schedule_event(
4208
				$start_at,
4209
				'daily',
4210
				$this->get_action_tag( 'data_sync' )
4211
			);
4212
4213
			$this->_storage->store( 'sync_cron', (object) array(
4214
				'version'     => $this->get_plugin_version(),
4215
				'sdk_version' => $this->version,
4216
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
4217
				'on'          => true,
4218
			) );
4219
		}
4220
4221
		/**
4222
		 * Add the actual sync function to the cron job hook.
4223
		 *
4224
		 * @author Vova Feldman (@svovaf)
4225
		 * @since  1.1.7.3
4226
		 */
4227
		private function hook_callback_to_sync_cron() {
4228
			$this->add_action( 'data_sync', array( &$this, '_sync_cron' ) );
4229
		}
4230
4231
		/**
4232
		 * @author Vova Feldman (@svovaf)
4233
		 * @since  1.1.7.3
4234
		 */
4235
		private function clear_sync_cron() {
4236
			$this->_logger->entrance();
4237
4238
			if ( ! $this->is_sync_cron_on() ) {
4239
				return;
4240
			}
4241
4242
			$this->_storage->remove( 'sync_cron' );
4243
4244
			wp_clear_scheduled_hook( $this->get_action_tag( 'data_sync' ) );
4245
		}
4246
4247
		/**
4248
		 * Unix timestamp for next sync cron execution or false if not scheduled.
4249
		 *
4250
		 * @author Vova Feldman (@svovaf)
4251
		 * @since  1.1.7.3
4252
		 *
4253
		 * @return int|false
4254
		 */
4255
		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...
4256
			$this->_logger->entrance();
4257
4258
			if ( ! $this->is_sync_cron_on() ) {
4259
				return false;
4260
			}
4261
4262
			return wp_next_scheduled( $this->get_action_tag( 'data_sync' ) );
4263
		}
4264
4265
		/**
4266
		 * Unix timestamp for previous sync cron execution or false if never executed.
4267
		 *
4268
		 * @author Vova Feldman (@svovaf)
4269
		 * @since  1.1.7.3
4270
		 *
4271
		 * @return int|false
4272
		 */
4273
		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...
4274
			$this->_logger->entrance();
4275
4276
			return $this->_storage->get( 'sync_timestamp' );
4277
		}
4278
4279
		#endregion Daily Sync Cron ------------------------------------------------------------------
4280
4281
		#----------------------------------------------------------------------------------
4282
		#region Async Install Sync
4283
		#----------------------------------------------------------------------------------
4284
4285
		/**
4286
		 * @author Vova Feldman (@svovaf)
4287
		 * @since  1.1.7.3
4288
		 *
4289
		 * @return bool
4290
		 */
4291
		private function is_install_sync_scheduled() {
4292
			/**
4293
			 * @var object $cron_data
4294
			 */
4295
			$cron_data = $this->_storage->get( 'install_sync_cron', null );
4296
4297
			return ( ! is_null( $cron_data ) && true === $cron_data->on );
4298
		}
4299
4300
		/**
4301
		 * Instead of running blocking install sync event, execute non blocking scheduled wp-cron.
4302
		 *
4303
		 * @author Vova Feldman (@svovaf)
4304
		 * @since  1.1.7.3
4305
		 */
4306
		private function schedule_install_sync() {
4307
			$this->_logger->entrance();
4308
4309
			$this->clear_install_sync_cron();
4310
4311
			// Schedule immediate install sync.
4312
			wp_schedule_single_event(
4313
				WP_FS__SCRIPT_START_TIME,
4314
				$this->get_action_tag( 'install_sync' )
4315
			);
4316
4317
			$this->_storage->store( 'install_sync_cron', (object) array(
4318
				'version'     => $this->get_plugin_version(),
4319
				'sdk_version' => $this->version,
4320
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
4321
				'on'          => true,
4322
			) );
4323
		}
4324
4325
		/**
4326
		 * Unix timestamp for previous install sync cron execution or false if never executed.
4327
		 *
4328
		 * @todo   There's some very strange bug that $this->_storage->install_sync_timestamp value is not being
4329
		 *         updated. But for sure the sync event is working.
4330
		 *
4331
		 * @author Vova Feldman (@svovaf)
4332
		 * @since  1.1.7.3
4333
		 *
4334
		 * @return int|false
4335
		 */
4336
		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...
4337
			$this->_logger->entrance();
4338
4339
			return $this->_storage->get( 'install_sync_timestamp' );
4340
		}
4341
4342
		/**
4343
		 * Unix timestamp for next install sync cron execution or false if not scheduled.
4344
		 *
4345
		 * @author Vova Feldman (@svovaf)
4346
		 * @since  1.1.7.3
4347
		 *
4348
		 * @return int|false
4349
		 */
4350
		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...
4351
			$this->_logger->entrance();
4352
4353
			if ( ! $this->is_install_sync_scheduled() ) {
4354
				return false;
4355
			}
4356
4357
			return wp_next_scheduled( $this->get_action_tag( 'install_sync' ) );
4358
		}
4359
4360
		/**
4361
		 * Add the actual install sync function to the cron job hook.
4362
		 *
4363
		 * @author Vova Feldman (@svovaf)
4364
		 * @since  1.1.7.3
4365
		 */
4366
		private function hook_callback_to_install_sync() {
4367
			$this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) );
4368
		}
4369
4370
		/**
4371
		 * @author Vova Feldman (@svovaf)
4372
		 * @since  1.1.7.3
4373
		 */
4374
		private function clear_install_sync_cron() {
4375
			$this->_logger->entrance();
4376
4377
			if ( ! $this->is_install_sync_scheduled() ) {
4378
				return;
4379
			}
4380
4381
			$this->_storage->remove( 'install_sync_cron' );
4382
4383
			wp_clear_scheduled_hook( $this->get_action_tag( 'install_sync' ) );
4384
		}
4385
4386
		/**
4387
		 * @author Vova Feldman (@svovaf)
4388
		 * @since  1.1.7.3
4389
		 */
4390
		public function _run_sync_install() {
4391
			$this->_logger->entrance();
4392
4393
			// Update last install sync timestamp.
4394
			$this->_storage->install_sync_timestamp = time();
4395
4396
			$this->sync_install( array(), true );
4397
		}
4398
4399
		#endregion Async Install Sync ------------------------------------------------------------------
4400
4401
		/**
4402
		 * Show a notice that activation is currently pending.
4403
		 *
4404
		 * @author Vova Feldman (@svovaf)
4405
		 * @since  1.0.7
4406
		 *
4407
		 * @param bool|string $email
4408
		 * @param bool        $is_pending_trial Since 1.2.1.5
4409
		 */
4410
		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...
4411
			if ( ! is_string( $email ) ) {
4412
				$current_user = self::_get_current_wp_user();
4413
				$email        = $current_user->user_email;
4414
			}
4415
4416
			$this->_admin_notices->add_sticky(
4417
				sprintf(
4418
					$this->get_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message' ),
4419
					'<b>' . $this->get_plugin_name() . '</b>',
4420
					'<b>' . $email . '</b>',
4421
					( $is_pending_trial ?
4422
						$this->get_text_inline( 'start the trial', 'start-the-trial' ) :
4423
						$this->get_text_inline( 'complete the install', 'complete-the-install' ) )
4424
				),
4425
				'activation_pending',
4426
				'Thanks!'
4427
			);
4428
		}
4429
4430
		/**
4431
		 * Check if currently in plugin activation.
4432
		 *
4433
		 * @author Vova Feldman (@svovaf)
4434
		 * @since  1.1.4
4435
		 *
4436
		 * @return bool
4437
		 */
4438
		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...
4439
			return get_option( 'fs_'
4440
			                   . ( $this->is_plugin() ? '' : $this->_module_type . '_' )
4441
			                   . "{$this->_slug}_activated", false );
4442
		}
4443
4444
		/**
4445
		 *
4446
		 * NOTE: admin_menu action executed before admin_init.
4447
		 *
4448
		 * @author Vova Feldman (@svovaf)
4449
		 * @since  1.0.7
4450
		 */
4451
		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...
4452
			/**
4453
			 * Automatically redirect to connect/activation page after plugin activation.
4454
			 *
4455
			 * @since 1.1.7 Do NOT redirect to opt-in when running in network admin mode.
4456
			 */
4457
			if ( $this->is_plugin_activation() ) {
4458
				delete_option( 'fs_'
4459
				               . ( $this->is_plugin() ? '' : $this->_module_type . '_' )
4460
				               . "{$this->_slug}_activated" );
4461
4462
				if ( ! function_exists( 'is_network_admin' ) || ! is_network_admin() ) {
4463
					$this->_redirect_on_activation_hook();
4464
4465
					return;
4466
				}
4467
			}
4468
4469
			if ( fs_request_is_action( $this->get_unique_affix() . '_skip_activation' ) ) {
4470
				check_admin_referer( $this->get_unique_affix() . '_skip_activation' );
4471
4472
				$this->skip_connection();
4473
4474
				fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) );
4475
			}
4476
4477
			if ( ! $this->is_addon() && ! $this->is_registered() && ! $this->is_anonymous() ) {
4478
				if ( ! $this->is_pending_activation() ) {
4479
					if ( ! $this->_menu->is_main_settings_page() ) {
4480
						/**
4481
						 * If a user visits any other admin page before activating the premium-only theme with a valid
4482
						 * license, reactivate the previous theme.
4483
						 *
4484
						 * @author Leo Fajardo (@leorw)
4485
						 * @since  1.2.2
4486
						 */
4487
						if ( $this->is_theme()
4488
						     && $this->is_only_premium()
4489
						     && ! $this->has_settings_menu()
4490
						     && ! isset( $_REQUEST['fs_action'] )
4491
						     && $this->can_activate_previous_theme()
4492
						) {
4493
							$this->activate_previous_theme();
4494
4495
							return;
4496
						}
4497
4498
						if ( $this->is_plugin_new_install() || $this->is_only_premium() ) {
4499
							// Show notice for new plugin installations.
4500
							$this->_admin_notices->add(
4501
								sprintf(
4502
									$this->get_text_inline( 'You are just one step away - %s', 'you-are-step-away' ),
4503
									sprintf( '<b><a href="%s">%s</a></b>',
4504
										$this->get_activation_url(),
4505
										sprintf( $this->get_text_x_inline( 'Complete "%s" Activation Now',
4506
											'%s - plugin name. As complete "PluginX" activation now', 'activate-x-now' ), $this->get_plugin_name() )
4507
									)
4508
								),
4509
								'',
4510
								'update-nag'
4511
							);
4512
						} else {
4513
							if ( ! isset( $this->_storage->sticky_optin_added ) ) {
4514
								$this->_storage->sticky_optin_added = true;
4515
4516
								// Show notice for new plugin installations.
4517
								$this->_admin_notices->add_sticky(
4518
									sprintf(
4519
										$this->get_text_inline( 'We made a few tweaks to the %s, %s', 'few-plugin-tweaks' ),
4520
										$this->_module_type,
4521
										sprintf( '<b><a href="%s">%s</a></b>',
4522
											$this->get_activation_url(),
4523
											sprintf( $this->get_text_inline( 'Opt in to make "%s" Better!', 'optin-x-now' ), $this->get_plugin_name() )
4524
										)
4525
									),
4526
									'connect_account',
4527
									'',
4528
									'update-nag'
4529
								);
4530
							}
4531
4532
							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...
4533
								// Don't show admin nag if plugin update.
4534
								wp_enqueue_script( 'wp-pointer' );
4535
								wp_enqueue_style( 'wp-pointer' );
4536
4537
								$this->_enqueue_connect_essentials();
4538
4539
								add_action( 'admin_print_footer_scripts', array(
4540
									$this,
4541
									'_add_connect_pointer_script'
4542
								) );
4543
							}
4544
						}
4545
					}
4546
				}
4547
4548
				if ( $this->is_theme() &&
4549
				     $this->_menu->is_main_settings_page()
4550
				) {
4551
					$this->_show_theme_activation_optin_dialog();
4552
				}
4553
			}
4554
4555
			$this->_add_upgrade_action_link();
4556
		}
4557
4558
		/**
4559
		 * Enqueue connect requires scripts and styles.
4560
		 *
4561
		 * @author Vova Feldman (@svovaf)
4562
		 * @since  1.1.4
4563
		 */
4564
		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...
4565
			wp_enqueue_script( 'jquery' );
4566
			wp_enqueue_script( 'json2' );
4567
4568
			fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' );
4569
			fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' );
4570
4571
			fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' );
4572
		}
4573
4574
		/**
4575
		 * Add connect / opt-in pointer.
4576
		 *
4577
		 * @author Vova Feldman (@svovaf)
4578
		 * @since  1.1.4
4579
		 */
4580
		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...
4581
			$vars            = array( 'id' => $this->_module_id );
4582
			$pointer_content = fs_get_template( 'connect.php', $vars );
4583
			?>
4584
			<script type="text/javascript">// <![CDATA[
4585
				jQuery(document).ready(function ($) {
4586
					if ('undefined' !== typeof(jQuery().pointer)) {
4587
4588
						var element = <?php echo $this->apply_filters( 'optin_pointer_element', '$("#non_existing_element");' ) ?>;
4589
4590
						if (element.length > 0) {
4591
							var optin = $(element).pointer($.extend(true, {}, {
4592
								content     : <?php echo json_encode( $pointer_content ) ?>,
4593
								position    : {
4594
									edge : 'left',
4595
									align: 'center'
4596
								},
4597
								buttons     : function () {
4598
									// Don't show pointer buttons.
4599
									return '';
4600
								},
4601
								pointerWidth: 482
4602
							}, <?php echo $this->apply_filters( 'optin_pointer_options_json', '{}' ) ?>));
4603
4604
							<?php
4605
							echo $this->apply_filters( 'optin_pointer_execute', "
4606
4607
							optin.pointer('open');
4608
4609
							// Tag the opt-in pointer with custom class.
4610
							$('.wp-pointer #fs_connect')
4611
								.parents('.wp-pointer.wp-pointer-top')
4612
								.addClass('fs-opt-in-pointer');
4613
4614
							", 'element', 'optin' ) ?>
4615
						}
4616
					}
4617
				});
4618
				// ]]></script>
4619
			<?php
4620
		}
4621
4622
		/**
4623
		 * Return current page's URL.
4624
		 *
4625
		 * @author Vova Feldman (@svovaf)
4626
		 * @since  1.0.7
4627
		 *
4628
		 * @return string
4629
		 */
4630
		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...
4631
			$url = 'http';
4632
4633
			if ( isset( $_SERVER["HTTPS"] ) ) {
4634
				if ( $_SERVER["HTTPS"] == "on" ) {
4635
					$url .= "s";
4636
				}
4637
			}
4638
			$url .= "://";
4639
			if ( $_SERVER["SERVER_PORT"] != "80" ) {
4640
				$url .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
4641
			} else {
4642
				$url .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
4643
			}
4644
4645
			return esc_url( $url );
4646
		}
4647
4648
		/**
4649
		 * Check if the current page is the plugin's main admin settings page.
4650
		 *
4651
		 * @author Vova Feldman (@svovaf)
4652
		 * @since  1.0.7
4653
		 *
4654
		 * @return bool
4655
		 */
4656
		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...
4657
			return fs_is_plugin_page( $this->_menu->get_raw_slug() ) ||
4658
			       fs_is_plugin_page( $this->_slug );
4659
		}
4660
4661
		/* Events
4662
		------------------------------------------------------------------------------------------------------------------*/
4663
		/**
4664
		 * Delete site install from Database.
4665
		 *
4666
		 * @author Vova Feldman (@svovaf)
4667
		 * @since  1.0.1
4668
		 *
4669
		 * @param bool $store
4670
		 */
4671
		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...
4672
			self::_delete_site_by_slug( $this->_slug, $this->_module_type, $store );
4673
		}
4674
4675
		/**
4676
		 * Delete site install from Database.
4677
		 *
4678
		 * @author Vova Feldman (@svovaf)
4679
		 * @since  1.2.2.7
4680
		 *
4681
		 * @param string $slug
4682
		 * @param string $module_type
4683
		 * @param bool   $store
4684
		 */
4685
		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...
4686
			$sites = self::get_all_sites( $module_type );
4687
4688
			if ( isset( $sites[ $slug ] ) ) {
4689
				unset( $sites[ $slug ] );
4690
			}
4691
4692
			self::set_account_option_by_module( $module_type, 'sites', $sites, $store );
4693
		}
4694
4695
		/**
4696
		 * Delete plugin's plans information.
4697
		 *
4698
		 * @param bool $store Flush to Database if true.
4699
		 *
4700
		 * @author Vova Feldman (@svovaf)
4701
		 * @since  1.0.9
4702
		 */
4703
		private function _delete_plans( $store = true ) {
4704
			$this->_logger->entrance();
4705
4706
			$plans = self::get_all_plans( $this->_module_type );
4707
4708
			unset( $plans[ $this->_slug ] );
4709
4710
			$this->set_account_option( 'plans', $plans, $store );
4711
		}
4712
4713
		/**
4714
		 * Delete all plugin licenses.
4715
		 *
4716
		 * @author Vova Feldman (@svovaf)
4717
		 * @since  1.0.9
4718
		 *
4719
		 * @param bool        $store
4720
		 * @param string|bool $plugin_slug
4721
		 */
4722
		private function _delete_licenses( $store = true, $plugin_slug = false ) {
4723
			$this->_logger->entrance();
4724
4725
			$all_licenses = self::get_all_licenses( $this->_module_type );
4726
4727
			if ( ! is_string( $plugin_slug ) ) {
4728
				$plugin_slug = $this->_slug;
4729
			}
4730
4731
			unset( $all_licenses[ $plugin_slug ] );
4732
4733
			$this->set_account_option( 'licenses', $all_licenses, $store );
4734
		}
4735
4736
		/**
4737
		 * Check if Freemius was added on new plugin installation.
4738
		 *
4739
		 * @author Vova Feldman (@svovaf)
4740
		 * @since  1.1.5
4741
		 *
4742
		 * @return bool
4743
		 */
4744
		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...
4745
			return isset( $this->_storage->is_plugin_new_install ) &&
4746
			       $this->_storage->is_plugin_new_install;
4747
		}
4748
4749
		/**
4750
		 * Check if it's the first plugin release that is running Freemius.
4751
		 *
4752
		 * @author Vova Feldman (@svovaf)
4753
		 * @since  1.2.1.5
4754
		 *
4755
		 * @return bool
4756
		 */
4757
		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...
4758
			return empty( $this->_storage->plugin_last_version );
4759
		}
4760
4761
		/**
4762
		 * @author Leo Fajardo (@leorw)
4763
		 * @since  1.2.2
4764
		 *
4765
		 * @return bool|string
4766
		 */
4767
		private function get_previous_theme_slug() {
4768
			return isset( $this->_storage->previous_theme ) ?
4769
				$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...
4770
				false;
4771
		}
4772
4773
		/**
4774
		 * @author Leo Fajardo (@leorw)
4775
		 * @since  1.2.2
4776
		 *
4777
		 * @return string
4778
		 */
4779
		private function can_activate_previous_theme() {
4780
			$slug = $this->get_previous_theme_slug();
4781
			if ( false !== $slug && current_user_can( 'switch_themes' ) ) {
4782
				$theme_instance = wp_get_theme( $slug );
4783
4784
				return $theme_instance->exists();
4785
			}
4786
4787
			return false;
4788
		}
4789
4790
		/**
4791
		 * @author Leo Fajardo (@leorw)
4792
		 * @since  1.2.2
4793
		 *
4794
		 * @return string
4795
		 */
4796
		private function activate_previous_theme() {
4797
			switch_theme( $this->get_previous_theme_slug() );
4798
			unset( $this->_storage->previous_theme );
4799
4800
			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...
4801
			if ( 'themes.php' === $pagenow ) {
4802
				/**
4803
				 * Refresh the active theme information.
4804
				 *
4805
				 * @author Leo Fajardo (@leorw)
4806
				 * @since  1.2.2
4807
				 */
4808
				fs_redirect( admin_url( $pagenow ) );
4809
			}
4810
		}
4811
4812
		/**
4813
		 * @author Leo Fajardo (@leorw)
4814
		 * @since  1.2.2
4815
		 *
4816
		 * @return string
4817
		 */
4818
		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...
4819
			if ( ! $this->can_activate_previous_theme() ) {
4820
				return '';
4821
			}
4822
4823
			/**
4824
			 * Activation URL
4825
			 *
4826
			 * @author Leo Fajardo (@leorw)
4827
			 * @since  1.2.2
4828
			 */
4829
			return wp_nonce_url(
4830
				admin_url( 'themes.php?action=activate&stylesheet=' . urlencode( $this->get_previous_theme_slug() ) ),
4831
				'switch-theme_' . $this->get_previous_theme_slug()
4832
			);
4833
		}
4834
4835
		/**
4836
		 * Saves the slug of the previous theme if it still exists so that it can be used by the logic in the opt-in
4837
		 * form that decides whether to add a close button to the opt-in dialog or not. So after a premium-only theme is
4838
		 * activated, the close button will appear and will reactivate the previous theme if clicked. If the previous
4839
		 * theme doesn't exist, then there will be no close button.
4840
		 *
4841
		 * @author Leo Fajardo (@leorw)
4842
		 * @since  1.2.2
4843
		 *
4844
		 * @param  string        $slug_or_name Old theme's slug or name.
4845
		 * @param  bool|WP_Theme $old_theme    WP_Theme instance of the old theme if it still exists.
4846
		 */
4847
		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...
4848
			$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...
4849
				$old_theme->get_stylesheet() :
4850
				$slug_or_name;
4851
4852
			$this->_activate_plugin_event_hook();
4853
		}
4854
4855
		/**
4856
		 * Plugin activated hook.
4857
		 *
4858
		 * @author Vova Feldman (@svovaf)
4859
		 * @since  1.0.1
4860
		 *
4861
		 * @uses   FS_Api
4862
		 */
4863
		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...
4864
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4865
4866
			if ( ! $this->is_user_admin() ) {
4867
				return;
4868
			}
4869
4870
			$this->unregister_uninstall_hook();
4871
4872
			// Clear API cache on activation.
4873
			FS_Api::clear_cache();
4874
4875
			$is_premium_version_activation = ( current_filter() !== ( 'activate_' . $this->_free_plugin_basename ) );
4876
4877
			$this->_logger->info( 'Activating ' . ( $is_premium_version_activation ? 'premium' : 'free' ) . ' plugin version.' );
4878
4879
			// 1. If running in the activation of the FREE module, get the basename of the PREMIUM.
4880
			// 2. If running in the activation of the PREMIUM module, get the basename of the FREE.
4881
			$other_version_basename = $is_premium_version_activation ?
4882
				$this->_free_plugin_basename :
4883
				$this->premium_plugin_basename();
4884
4885
			/**
4886
			 * If the other module version is activate, deactivate it.
4887
			 *
4888
			 * @author Leo Fajardo (@leorw)
4889
			 * @since  1.2.2
4890
			 */
4891
			if ( is_plugin_active( $other_version_basename ) ) {
4892
				deactivate_plugins( $other_version_basename );
4893
			}
4894
4895
			if ( $this->is_registered() ) {
4896
				if ( $is_premium_version_activation ) {
4897
					$this->reconnect_locally();
4898
				}
4899
4900
4901
				// 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...
4902
//				$this->sync_install( array(), true );
4903
				$this->schedule_install_sync();
4904
4905
				// If activating the premium module version, add an admin notice to congratulate for an upgrade completion.
4906
				if ( $is_premium_version_activation ) {
4907
					$this->_admin_notices->add(
4908
						sprintf( $this->get_text_inline( 'The upgrade of %s was successfully completed.', 'successful-version-upgrade-message' ), sprintf( '<b>%s</b>', $this->_plugin->title ) ),
4909
						$this->get_text_x_inline( 'W00t',
4910
							'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!'
4911
					);
4912
				}
4913
			} else if ( $this->is_anonymous() ) {
4914
				/**
4915
				 * Reset "skipped" click cache on the following:
4916
				 *  1. Freemius DEV mode.
4917
				 *  2. WordPress DEBUG mode.
4918
				 *  3. If a plugin and the user skipped the exact same version before.
4919
				 *
4920
				 * @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).
4921
				 *
4922
				 * @todo 4. If explicitly asked to retry after every activation.
4923
				 */
4924
				if ( WP_FS__DEV_MODE ||
4925
				     (
4926
				     	( $this->is_plugin() || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) &&
4927
				        $this->get_plugin_version() == $this->_storage->is_anonymous['version']
4928
				     )
4929
				) {
4930
					$this->reset_anonymous_mode();
4931
				}
4932
			}
4933
4934
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
4935
				/**
4936
				 * If no previous version of plugin's version exist, it means that it's either
4937
				 * the first time that the plugin installed on the site, or the plugin was installed
4938
				 * before but didn't have Freemius integrated.
4939
				 *
4940
				 * Since register_activation_hook() do NOT fires on updates since 3.1, and only fires
4941
				 * on manual activation via the dashboard, is_plugin_activation() is TRUE
4942
				 * only after immediate activation.
4943
				 *
4944
				 * @since 1.1.4
4945
				 * @link  https://make.wordpress.org/core/2010/10/27/plugin-activation-hooks-no-longer-fire-for-updates/
4946
				 */
4947
				$this->_storage->is_plugin_new_install = empty( $this->_storage->plugin_last_version );
4948
			}
4949
4950
			if ( ! $this->_anonymous_mode &&
4951
			     $this->has_api_connectivity( WP_FS__DEV_MODE ) &&
4952
			     ! $this->_isAutoInstall
4953
			) {
4954
				// Store hint that the plugin was just activated to enable auto-redirection to settings.
4955
				add_option( 'fs_'
4956
				            . ( $this->is_plugin() ? '' : $this->_module_type . '_' )
4957
				            . "{$this->_slug}_activated", true );
4958
			}
4959
4960
			/**
4961
			 * Activation hook is executed after the plugin's main file is loaded, therefore,
4962
			 * after the plugin was loaded. The logic is located at activate_plugin()
4963
			 * ./wp-admin/includes/plugin.php.
4964
			 *
4965
			 * @author Vova Feldman (@svovaf)
4966
			 * @since  1.1.9
4967
			 */
4968
			$this->_storage->was_plugin_loaded = true;
4969
		}
4970
4971
		/**
4972
		 * Delete account.
4973
		 *
4974
		 * @author Vova Feldman (@svovaf)
4975
		 * @since  1.0.3
4976
		 *
4977
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
4978
		 */
4979
		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...
4980
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4981
4982
			if ( $check_user && ! $this->is_user_admin() ) {
4983
				return;
4984
			}
4985
4986
			$this->do_action( 'before_account_delete' );
4987
4988
			// Clear all admin notices.
4989
			$this->_admin_notices->clear_all_sticky();
4990
4991
			$this->_delete_site( false );
4992
4993
			$this->_delete_plans( false );
4994
4995
			$this->_delete_licenses( false );
4996
4997
			// Delete add-ons related to plugin's account.
4998
			$this->_delete_account_addons( false );
4999
5000
			// @todo Delete plans and licenses of add-ons.
5001
5002
			self::$_accounts->store();
5003
5004
			/**
5005
			 * IMPORTANT:
5006
			 *  Clear crons must be executed before clearing all storage.
5007
			 *  Otherwise, the cron will not be cleared.
5008
			 */
5009
			$this->clear_sync_cron();
5010
			$this->clear_install_sync_cron();
5011
5012
			// Clear all storage data.
5013
			$this->_storage->clear_all( true, array(
5014
				'connectivity_test',
5015
				'is_on',
5016
			) );
5017
5018
			// Send delete event.
5019
			$this->get_api_site_scope()->call( '/', 'delete' );
5020
5021
			$this->do_action( 'after_account_delete' );
5022
		}
5023
5024
		/**
5025
		 * Plugin deactivation hook.
5026
		 *
5027
		 * @author Vova Feldman (@svovaf)
5028
		 * @since  1.0.1
5029
		 */
5030
		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...
5031
			$this->_logger->entrance( 'slug = ' . $this->_slug );
5032
5033
			if ( ! current_user_can( 'activate_plugins' ) ) {
5034
				return;
5035
			}
5036
5037
			$this->_admin_notices->clear_all_sticky();
5038
			if ( isset( $this->_storage->sticky_optin_added ) ) {
5039
				unset( $this->_storage->sticky_optin_added );
5040
			}
5041
5042
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
5043
				// Remember that plugin was already installed.
5044
				$this->_storage->is_plugin_new_install = false;
5045
			}
5046
5047
			// Hook to plugin uninstall.
5048
			register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) );
5049
5050
			$this->clear_module_main_file_cache();
5051
			$this->clear_sync_cron();
5052
			$this->clear_install_sync_cron();
5053
5054
			if ( $this->is_registered() ) {
5055
				// Send deactivation event.
5056
				$this->sync_install( array(
5057
					'is_active' => false,
5058
				) );
5059
			} else {
5060
				if ( ! $this->has_api_connectivity() ) {
5061
					// Reset connectivity test cache.
5062
					unset( $this->_storage->connectivity_test );
5063
				}
5064
			}
5065
5066
			// Clear API cache on deactivation.
5067
			FS_Api::clear_cache();
5068
5069
			$this->remove_sdk_reference();
5070
		}
5071
5072
		/**
5073
		 * @author Vova Feldman (@svovaf)
5074
		 * @since  1.1.6
5075
		 */
5076
		private function remove_sdk_reference() {
5077
			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...
5078
5079
			foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) {
5080
				if ( $this->_plugin_basename == $data->plugin_path ) {
5081
					unset( $fs_active_plugins->plugins[ $sdk_path ] );
5082
					break;
5083
				}
5084
			}
5085
5086
			fs_fallback_to_newest_active_sdk();
5087
		}
5088
5089
		/**
5090
		 * @author Vova Feldman (@svovaf)
5091
		 * @since  1.1.3
5092
		 *
5093
		 * @param bool $is_anonymous
5094
		 */
5095
		private function set_anonymous_mode( $is_anonymous = true ) {
5096
			// Store information regarding skip to try and opt-in the user
5097
			// again in the future.
5098
			$this->_storage->is_anonymous = array(
5099
				'is'        => $is_anonymous,
5100
				'timestamp' => WP_FS__SCRIPT_START_TIME,
5101
				'version'   => $this->get_plugin_version(),
5102
			);
5103
5104
			// Update anonymous mode cache.
5105
			$this->_is_anonymous = $is_anonymous;
5106
		}
5107
5108
		/**
5109
		 * @author Vova Feldman (@svovaf)
5110
		 * @since  1.1.3
5111
		 */
5112
		private function reset_anonymous_mode() {
5113
			unset( $this->_storage->is_anonymous );
5114
5115
			/**
5116
			 * Ensure that this field is also "false", otherwise, if the current module's type is "theme" and the module
5117
			 * has no menus, the opt-in popup will not be shown immediately (in this case, the user will have to click
5118
			 * on the admin notice that contains the opt-in link in order to trigger the opt-in popup).
5119
			 *
5120
			 * @author Leo Fajardo (@leorw)
5121
			 * @since  1.2.2
5122
			 */
5123
			unset( $this->_is_anonymous );
5124
		}
5125
5126
		/**
5127
		 * Clears the anonymous mode and redirects to the opt-in screen.
5128
		 *
5129
		 * @author Vova Feldman (@svovaf)
5130
		 * @since  1.1.7
5131
		 */
5132
		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...
5133
			if ( ! $this->is_anonymous() ) {
5134
				return;
5135
			}
5136
5137
			$this->reset_anonymous_mode();
5138
5139
			fs_redirect( $this->get_activation_url() );
5140
		}
5141
5142
		/**
5143
		 * Skip account connect, and set anonymous mode.
5144
		 *
5145
		 * @author Vova Feldman (@svovaf)
5146
		 * @since  1.1.1
5147
		 */
5148
		private function skip_connection() {
5149
			$this->_logger->entrance();
5150
5151
			$this->_admin_notices->remove_sticky( 'connect_account' );
5152
5153
			$this->set_anonymous_mode();
5154
5155
			// Send anonymous skip event.
5156
			// No user identified info nor any tracking will be sent after the user skips the opt-in.
5157
			$this->get_api_plugin_scope()->call( 'skip.json', 'put', array(
5158
				'uid' => $this->get_anonymous_id(),
5159
			) );
5160
		}
5161
5162
		/**
5163
		 * Plugin version update hook.
5164
		 *
5165
		 * @author Vova Feldman (@svovaf)
5166
		 * @since  1.0.4
5167
		 */
5168
		private function update_plugin_version_event() {
5169
			$this->_logger->entrance();
5170
5171
			if ( ! $this->is_registered() ) {
5172
				return;
5173
			}
5174
5175
			$this->schedule_install_sync();
5176
//			$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...
5177
		}
5178
5179
		/**
5180
		 * Return a list of modified plugins since the last sync.
5181
		 *
5182
		 * Note:
5183
		 *  There's no point to store a plugins counter since even if the number of
5184
		 *  plugins didn't change, we still need to check if the versions are all the
5185
		 *  same and the activity state is similar.
5186
		 *
5187
		 * @author Vova Feldman (@svovaf)
5188
		 * @since  1.1.8
5189
		 *
5190
		 * @return array|false
5191
		 */
5192
		private function get_plugins_data_for_api() {
5193
			// Alias.
5194
			$option_name = 'all_plugins';
5195
5196
			$all_cached_plugins = self::$_accounts->get_option( $option_name );
5197
5198
			if ( ! is_object( $all_cached_plugins ) ) {
5199
				$all_cached_plugins = (object) array(
5200
					'timestamp' => '',
5201
					'md5'       => '',
5202
					'plugins'   => array(),
5203
				);
5204
			}
5205
5206
			$time = time();
5207
5208
			if ( ! empty( $all_cached_plugins->timestamp ) &&
5209
			     ( $time - $all_cached_plugins->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC
5210
			) {
5211
				// Don't send plugin updates if last update was in the past 5 min.
5212
				return false;
5213
			}
5214
5215
			// Write timestamp to lock the logic.
5216
			$all_cached_plugins->timestamp = $time;
5217
			self::$_accounts->set_option( $option_name, $all_cached_plugins, true );
5218
5219
			// Reload options from DB.
5220
			self::$_accounts->load( true );
5221
			$all_cached_plugins = self::$_accounts->get_option( $option_name );
5222
5223
			if ( $time != $all_cached_plugins->timestamp ) {
5224
				// If timestamp is different, then another thread captured the lock.
5225
				return false;
5226
			}
5227
5228
			// Check if there's a change in plugins.
5229
			$all_plugins = self::get_all_plugins();
5230
5231
			// Check if plugins changed.
5232
			ksort( $all_plugins );
5233
5234
			$plugins_signature = '';
5235
			foreach ( $all_plugins as $basename => $data ) {
5236
				$plugins_signature .= $data['slug'] . ',' .
5237
				                      $data['Version'] . ',' .
5238
				                      ( $data['is_active'] ? '1' : '0' ) . ';';
5239
			}
5240
5241
			// Check if plugins status changed (version or active/inactive).
5242
			$plugins_changed = ( $all_cached_plugins->md5 !== md5( $plugins_signature ) );
5243
5244
			$plugins_update_data = array();
5245
5246
			if ( $plugins_changed ) {
5247
				// Change in plugins, report changes.
5248
5249
				// Update existing plugins info.
5250
				foreach ( $all_cached_plugins->plugins as $basename => $data ) {
5251
					if ( ! isset( $all_plugins[ $basename ] ) ) {
5252
						// Plugin uninstalled.
5253
						$uninstalled_plugin_data                   = $data;
5254
						$uninstalled_plugin_data['is_active']      = false;
5255
						$uninstalled_plugin_data['is_uninstalled'] = true;
5256
						$plugins_update_data[]                     = $uninstalled_plugin_data;
5257
5258
						unset( $all_plugins[ $basename ] );
5259
						unset( $all_cached_plugins->plugins[ $basename ] );
5260
					} else if ( $data['is_active'] !== $all_plugins[ $basename ]['is_active'] ||
5261
					            $data['version'] !== $all_plugins[ $basename ]['Version']
5262
					) {
5263
						// Plugin activated or deactivated, or version changed.
5264
						$all_cached_plugins->plugins[ $basename ]['is_active'] = $all_plugins[ $basename ]['is_active'];
5265
						$all_cached_plugins->plugins[ $basename ]['version']   = $all_plugins[ $basename ]['Version'];
5266
5267
						$plugins_update_data[] = $all_cached_plugins->plugins[ $basename ];
5268
					}
5269
				}
5270
5271
				// Find new plugins that weren't yet seen before.
5272
				foreach ( $all_plugins as $basename => $data ) {
5273
					if ( ! isset( $all_cached_plugins->plugins[ $basename ] ) ) {
5274
						// New plugin.
5275
						$new_plugin = array(
5276
							'slug'           => $data['slug'],
5277
							'version'        => $data['Version'],
5278
							'title'          => $data['Name'],
5279
							'is_active'      => $data['is_active'],
5280
							'is_uninstalled' => false,
5281
						);
5282
5283
						$plugins_update_data[]                    = $new_plugin;
5284
						$all_cached_plugins->plugins[ $basename ] = $new_plugin;
5285
					}
5286
				}
5287
5288
				$all_cached_plugins->md5       = md5( $plugins_signature );
5289
				$all_cached_plugins->timestamp = $time;
5290
				self::$_accounts->set_option( $option_name, $all_cached_plugins, true );
5291
			}
5292
5293
			return $plugins_update_data;
5294
		}
5295
5296
		/**
5297
		 * Return a list of modified themes since the last sync.
5298
		 *
5299
		 * Note:
5300
		 *  There's no point to store a themes counter since even if the number of
5301
		 *  themes didn't change, we still need to check if the versions are all the
5302
		 *  same and the activity state is similar.
5303
		 *
5304
		 * @author Vova Feldman (@svovaf)
5305
		 * @since  1.1.8
5306
		 *
5307
		 * @return array|false
5308
		 */
5309
		private function get_themes_data_for_api() {
5310
			// Alias.
5311
			$option_name = 'all_themes';
5312
5313
			$all_cached_themes = self::$_accounts->get_option( $option_name );
5314
5315
			if ( ! is_object( $all_cached_themes ) ) {
5316
				$all_cached_themes = (object) array(
5317
					'timestamp' => '',
5318
					'md5'       => '',
5319
					'themes'    => array(),
5320
				);
5321
			}
5322
5323
			$time = time();
5324
5325
			if ( ! empty( $all_cached_themes->timestamp ) &&
5326
			     ( $time - $all_cached_themes->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC
5327
			) {
5328
				// Don't send theme updates if last update was in the past 5 min.
5329
				return false;
5330
			}
5331
5332
			// Write timestamp to lock the logic.
5333
			$all_cached_themes->timestamp = $time;
5334
			self::$_accounts->set_option( $option_name, $all_cached_themes, true );
5335
5336
			// Reload options from DB.
5337
			self::$_accounts->load( true );
5338
			$all_cached_themes = self::$_accounts->get_option( $option_name );
5339
5340
			if ( $time != $all_cached_themes->timestamp ) {
5341
				// If timestamp is different, then another thread captured the lock.
5342
				return false;
5343
			}
5344
5345
			// Get active theme.
5346
			$active_theme            = wp_get_theme();
5347
			$active_theme_stylesheet = $active_theme->get_stylesheet();
5348
5349
			// Check if there's a change in themes.
5350
			$all_themes = wp_get_themes();
5351
5352
			// Check if themes changed.
5353
			ksort( $all_themes );
5354
5355
			$themes_signature = '';
5356
			foreach ( $all_themes as $slug => $data ) {
5357
				$is_active = ( $slug === $active_theme_stylesheet );
5358
				$themes_signature .= $slug . ',' .
5359
				                     $data->version . ',' .
5360
				                     ( $is_active ? '1' : '0' ) . ';';
5361
			}
5362
5363
			// Check if themes status changed (version or active/inactive).
5364
			$themes_changed = ( $all_cached_themes->md5 !== md5( $themes_signature ) );
5365
5366
			$themes_update_data = array();
5367
5368
			if ( $themes_changed ) {
5369
				// Change in themes, report changes.
5370
5371
				// Update existing themes info.
5372
				foreach ( $all_cached_themes->themes as $slug => $data ) {
5373
					$is_active = ( $slug === $active_theme_stylesheet );
5374
5375
					if ( ! isset( $all_themes[ $slug ] ) ) {
5376
						// Plugin uninstalled.
5377
						$uninstalled_theme_data                   = $data;
5378
						$uninstalled_theme_data['is_active']      = false;
5379
						$uninstalled_theme_data['is_uninstalled'] = true;
5380
						$themes_update_data[]                     = $uninstalled_theme_data;
5381
5382
						unset( $all_themes[ $slug ] );
5383
						unset( $all_cached_themes->themes[ $slug ] );
5384
					} else if ( $data['is_active'] !== $is_active ||
5385
					            $data['version'] !== $all_themes[ $slug ]->version
5386
					) {
5387
						// Plugin activated or deactivated, or version changed.
5388
5389
						$all_cached_themes->themes[ $slug ]['is_active'] = $is_active;
5390
						$all_cached_themes->themes[ $slug ]['version']   = $all_themes[ $slug ]->version;
5391
5392
						$themes_update_data[] = $all_cached_themes->themes[ $slug ];
5393
					}
5394
				}
5395
5396
				// Find new themes that weren't yet seen before.
5397
				foreach ( $all_themes as $slug => $data ) {
5398
					if ( ! isset( $all_cached_themes->themes[ $slug ] ) ) {
5399
						$is_active = ( $slug === $active_theme_stylesheet );
5400
5401
						// New plugin.
5402
						$new_plugin = array(
5403
							'slug'           => $slug,
5404
							'version'        => $data->version,
5405
							'title'          => $data->name,
5406
							'is_active'      => $is_active,
5407
							'is_uninstalled' => false,
5408
						);
5409
5410
						$themes_update_data[]               = $new_plugin;
5411
						$all_cached_themes->themes[ $slug ] = $new_plugin;
5412
					}
5413
				}
5414
5415
				$all_cached_themes->md5       = md5( $themes_signature );
5416
				$all_cached_themes->timestamp = time();
5417
				self::$_accounts->set_option( $option_name, $all_cached_themes, true );
5418
			}
5419
5420
			return $themes_update_data;
5421
		}
5422
5423
		/**
5424
		 * Update install details.
5425
		 *
5426
		 * @author Vova Feldman (@svovaf)
5427
		 * @since  1.1.2
5428
		 *
5429
		 * @param string[] string           $override
5430
		 * @param bool     $include_plugins Since 1.1.8 by default include plugin changes.
5431
		 * @param bool     $include_themes  Since 1.1.8 by default include plugin changes.
5432
		 *
5433
		 * @return array
5434
		 */
5435
		private function get_install_data_for_api(
5436
			array $override,
5437
			$include_plugins = true,
5438
			$include_themes = true
5439
		) {
5440
			/**
5441
			 * @since 1.1.8 Also send plugin updates.
5442
			 */
5443
			if ( $include_plugins && ! isset( $override['plugins'] ) ) {
5444
				$plugins = $this->get_plugins_data_for_api();
5445
				if ( ! empty( $plugins ) ) {
5446
					$override['plugins'] = $plugins;
5447
				}
5448
			}
5449
			/**
5450
			 * @since 1.1.8 Also send themes updates.
5451
			 */
5452
			if ( $include_themes && ! isset( $override['themes'] ) ) {
5453
				$themes = $this->get_themes_data_for_api();
5454
				if ( ! empty( $themes ) ) {
5455
					$override['themes'] = $themes;
5456
				}
5457
			}
5458
5459
			return array_merge( array(
5460
				'version'                      => $this->get_plugin_version(),
5461
				'is_premium'                   => $this->is_premium(),
5462
				'language'                     => get_bloginfo( 'language' ),
5463
				'charset'                      => get_bloginfo( 'charset' ),
5464
				'platform_version'             => get_bloginfo( 'version' ),
5465
				'sdk_version'                  => $this->version,
5466
				'programming_language_version' => phpversion(),
5467
				'title'                        => get_bloginfo( 'name' ),
5468
				'url'                          => get_site_url(),
5469
				// Special params.
5470
				'is_active'                    => true,
5471
				'is_disconnected'              => $this->is_tracking_prohibited(),
5472
				'is_uninstalled'               => false,
5473
			), $override );
5474
		}
5475
5476
		/**
5477
		 * Update install only if changed.
5478
		 *
5479
		 * @author Vova Feldman (@svovaf)
5480
		 * @since  1.0.9
5481
		 *
5482
		 * @param string[] string $override
5483
		 * @param bool     $flush
5484
		 *
5485
		 * @return false|object|string
5486
		 */
5487
		private function send_install_update( $override = array(), $flush = false ) {
5488
			$this->_logger->entrance();
5489
5490
			$check_properties = $this->get_install_data_for_api( $override );
5491
5492
			if ( $flush ) {
5493
				$params = $check_properties;
5494
			} else {
5495
				$params           = array();
5496
				$special          = array();
5497
				$special_override = false;
5498
5499
				foreach ( $check_properties as $p => $v ) {
5500
					if ( property_exists( $this->_site, $p ) ) {
5501
						if ( ( is_bool( $this->_site->{$p} ) || ! empty( $this->_site->{$p} ) ) &&
5502
						     $this->_site->{$p} != $v
5503
						) {
5504
							$this->_site->{$p} = $v;
5505
							$params[ $p ]      = $v;
5506
						}
5507
					} else {
5508
						$special[ $p ] = $v;
5509
5510
						if ( isset( $override[ $p ] ) ||
5511
						     'plugins' === $p ||
5512
						     'themes' === $p
5513
						) {
5514
							$special_override = true;
5515
						}
5516
					}
5517
				}
5518
5519
				if ( $special_override || 0 < count( $params ) ) {
5520
					// Add special params only if has at least one
5521
					// standard param, or if explicitly requested to
5522
					// override a special param or a param which is not exist
5523
					// in the install object.
5524
					$params = array_merge( $params, $special );
5525
				}
5526
			}
5527
5528
			if ( 0 < count( $params ) ) {
5529
				// Update last install sync timestamp.
5530
				$this->_storage->install_sync_timestamp = time();
5531
5532
				$params['uid'] = $this->get_anonymous_id();
5533
5534
				// Send updated values to FS.
5535
				$site = $this->get_api_site_scope()->call( '/', 'put', $params );
5536
5537
				if ( $this->is_api_result_entity( $site ) ) {
5538
					// I successfully sent install update, clear scheduled sync if exist.
5539
					$this->clear_install_sync_cron();
5540
				}
5541
5542
				return $site;
5543
			}
5544
5545
			return false;
5546
		}
5547
5548
		/**
5549
		 * Update install only if changed.
5550
		 *
5551
		 * @author Vova Feldman (@svovaf)
5552
		 * @since  1.0.9
5553
		 *
5554
		 * @param string[] string $override
5555
		 * @param bool     $flush
5556
		 */
5557
		private function sync_install( $override = array(), $flush = false ) {
5558
			$this->_logger->entrance();
5559
5560
			$site = $this->send_install_update( $override, $flush );
5561
5562
			if ( false === $site ) {
5563
				// No sync required.
5564
				return;
5565
			}
5566
5567
			if ( ! $this->is_api_result_entity( $site ) ) {
5568
				// Failed to sync, don't update locally.
5569
				return;
5570
			}
5571
5572
			$plan              = $this->get_plan();
5573
			$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...
5574
			$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...
5575
5576
			$this->_store_site( true );
5577
		}
5578
5579
		/**
5580
		 * Track install's custom event.
5581
		 *
5582
		 * IMPORTANT:
5583
		 *      Custom event tracking is currently only supported for specific clients.
5584
		 *      If you are not one of them, please don't use this method. If you will,
5585
		 *      the API will simply ignore your request based on the plugin ID.
5586
		 *
5587
		 * Need custom tracking for your plugin or theme?
5588
		 *      If you are interested in custom event tracking please contact [email protected]
5589
		 *      for further details.
5590
		 *
5591
		 * @author Vova Feldman (@svovaf)
5592
		 * @since  1.2.1
5593
		 *
5594
		 * @param string $name       Event name.
5595
		 * @param array  $properties Associative key/value array with primitive values only
5596
		 * @param bool   $process_at A valid future date-time in the following format Y-m-d H:i:s.
5597
		 * @param bool   $once       If true, event will be tracked only once. IMPORTANT: Still trigger the API call.
5598
		 *
5599
		 * @return object|false Event data or FALSE on failure.
5600
		 *
5601
		 * @throws \Freemius_InvalidArgumentException
5602
		 */
5603
		public function track_event( $name, $properties = array(), $process_at = false, $once = false ) {
5604
			$this->_logger->entrance( http_build_query( array( 'name' => $name, 'once' => $once ) ) );
5605
5606
			if ( ! $this->is_registered() ) {
5607
				return false;
5608
			}
5609
5610
			$event = array( 'type' => $name );
5611
5612
			if ( is_numeric( $process_at ) && $process_at > time() ) {
5613
				$event['process_at'] = $process_at;
5614
			}
5615
5616
			if ( $once ) {
5617
				$event['once'] = true;
5618
			}
5619
5620
			if ( ! empty( $properties ) ) {
5621
				// Verify associative array values are primitive.
5622
				foreach ( $properties as $k => $v ) {
5623
					if ( ! is_scalar( $v ) ) {
5624
						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...
5625
					}
5626
				}
5627
5628
				$event['properties'] = $properties;
5629
			}
5630
5631
			$result = $this->get_api_site_scope()->call( 'events.json', 'post', $event );
5632
5633
			return $this->is_api_error( $result ) ?
5634
				false :
5635
				$result;
5636
		}
5637
5638
		/**
5639
		 * Track install's custom event only once, but it still triggers the API call.
5640
		 *
5641
		 * IMPORTANT:
5642
		 *      Custom event tracking is currently only supported for specific clients.
5643
		 *      If you are not one of them, please don't use this method. If you will,
5644
		 *      the API will simply ignore your request based on the plugin ID.
5645
		 *
5646
		 * Need custom tracking for your plugin or theme?
5647
		 *      If you are interested in custom event tracking please contact [email protected]
5648
		 *      for further details.
5649
		 *
5650
		 * @author Vova Feldman (@svovaf)
5651
		 * @since  1.2.1
5652
		 *
5653
		 * @param string $name       Event name.
5654
		 * @param array  $properties Associative key/value array with primitive values only
5655
		 * @param bool   $process_at A valid future date-time in the following format Y-m-d H:i:s.
5656
		 *
5657
		 * @return object|false Event data or FALSE on failure.
5658
		 *
5659
		 * @throws \Freemius_InvalidArgumentException
5660
		 *
5661
		 * @user   Freemius::track_event()
5662
		 */
5663
		public function track_event_once( $name, $properties = array(), $process_at = false ) {
5664
			return $this->track_event( $name, $properties, $process_at, true );
5665
		}
5666
5667
		/**
5668
		 * Plugin uninstall hook.
5669
		 *
5670
		 * @author Vova Feldman (@svovaf)
5671
		 * @since  1.0.1
5672
		 *
5673
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
5674
		 */
5675
		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...
5676
			$this->_logger->entrance( 'slug = ' . $this->_slug );
5677
5678
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
5679
				return;
5680
			}
5681
5682
			$params           = array();
5683
			$uninstall_reason = null;
5684
			if ( isset( $this->_storage->uninstall_reason ) ) {
5685
				$uninstall_reason      = $this->_storage->uninstall_reason;
5686
				$params['reason_id']   = $uninstall_reason->id;
5687
				$params['reason_info'] = $uninstall_reason->info;
5688
			}
5689
5690
			if ( ! $this->is_registered() ) {
5691
				// Send anonymous uninstall event only if user submitted a feedback.
5692
				if ( isset( $uninstall_reason ) ) {
5693
					if ( isset( $uninstall_reason->is_anonymous ) && ! $uninstall_reason->is_anonymous ) {
5694
						$this->opt_in( false, false, false, false, true );
5695
					} else {
5696
						$params['uid'] = $this->get_anonymous_id();
5697
						$this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params );
5698
					}
5699
				}
5700
			} else {
5701
				// Send uninstall event.
5702
				$this->send_install_update( array_merge( $params, array(
5703
					'is_active'      => false,
5704
					'is_uninstalled' => true,
5705
				) ) );
5706
			}
5707
5708
			// @todo Decide if we want to delete plugin information from db.
5709
		}
5710
5711
		/**
5712
		 * @author Vova Feldman (@svovaf)
5713
		 * @since  1.1.1
5714
		 *
5715
		 * @return string
5716
		 */
5717
		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...
5718
			return "{$this->_slug}-premium/" . basename( $this->_free_plugin_basename );
5719
		}
5720
5721
		/**
5722
		 * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking.
5723
		 *
5724
		 * @author Vova Feldman (@svovaf)
5725
		 * @since  1.0.2
5726
		 */
5727
		public static function _uninstall_plugin_hook() {
5728
			self::_load_required_static();
5729
5730
			self::$_static_logger->entrance();
5731
5732
			if ( ! current_user_can( 'activate_plugins' ) ) {
5733
				return;
5734
			}
5735
5736
			$plugin_file = substr( current_filter(), strlen( 'uninstall_' ) );
5737
5738
			self::$_static_logger->info( 'plugin = ' . $plugin_file );
5739
5740
			define( 'WP_FS__UNINSTALL_MODE', true );
5741
5742
			$fs = self::get_instance_by_file( $plugin_file );
5743
5744
			if ( is_object( $fs ) ) {
5745
				self::require_plugin_essentials();
5746
5747
				if ( is_plugin_active( $fs->_free_plugin_basename ) ||
5748
				     is_plugin_active( $fs->premium_plugin_basename() )
5749
				) {
5750
					// Deleting Free or Premium plugin version while the other version still installed.
5751
					return;
5752
				}
5753
5754
				$fs->_uninstall_plugin_event();
5755
5756
				$fs->do_action( 'after_uninstall' );
5757
			}
5758
		}
5759
5760
		#----------------------------------------------------------------------------------
5761
		#region Plugin Information
5762
		#----------------------------------------------------------------------------------
5763
5764
		/**
5765
		 * Load WordPress core plugin.php essential module.
5766
		 *
5767
		 * @author Vova Feldman (@svovaf)
5768
		 * @since  1.1.1
5769
		 */
5770
		private static function require_plugin_essentials() {
5771
			if ( ! function_exists( 'get_plugins' ) ) {
5772
				self::$_static_logger->log( 'Including wp-admin/includes/plugin.php...' );
5773
5774
				require_once ABSPATH . 'wp-admin/includes/plugin.php';
5775
			}
5776
		}
5777
5778
		/**
5779
		 * Load WordPress core pluggable.php module.
5780
		 *
5781
		 * @author Vova Feldman (@svovaf)
5782
		 * @since  1.1.2
5783
		 */
5784
		private static function require_pluggable_essentials() {
5785
			if ( ! function_exists( 'wp_get_current_user' ) ) {
5786
				require_once ABSPATH . 'wp-includes/pluggable.php';
5787
			}
5788
		}
5789
5790
		/**
5791
		 * Return plugin data.
5792
		 *
5793
		 * @author Vova Feldman (@svovaf)
5794
		 * @since  1.0.1
5795
		 *
5796
		 * @return array
5797
		 */
5798
		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...
5799
			if ( ! isset( $this->_plugin_data ) ) {
5800
				self::require_plugin_essentials();
5801
5802
				if ( $this->is_plugin() ) {
5803
					/**
5804
					 * @author Vova Feldman (@svovaf)
5805
					 * @since  1.2.0 When using get_plugin_data() do NOT translate plugin data.
5806
					 *
5807
					 * @link   https://github.com/Freemius/wordpress-sdk/issues/77
5808
					 */
5809
					$plugin_data = get_plugin_data(
5810
						$this->_plugin_main_file_path,
5811
						false,
5812
						false
5813
					);
5814
				} else {
5815
					$theme_data = wp_get_theme();
5816
5817
					$plugin_data = array(
5818
						'Name'        => $theme_data->get( 'Name' ),
5819
						'Version'     => $theme_data->get( 'Version' ),
5820
						'Author'      => $theme_data->get( 'Author' ),
5821
						'Description' => $theme_data->get( 'Description' ),
5822
						'PluginURI'   => $theme_data->get( 'ThemeURI' ),
5823
					);
5824
				}
5825
5826
				$this->_plugin_data = $plugin_data;
5827
			}
5828
5829
			return $this->_plugin_data;
5830
		}
5831
5832
		/**
5833
		 * @author Vova Feldman (@svovaf)
5834
		 * @since  1.0.1
5835
		 * @since  1.2.2.5 If slug not set load slug by module ID.
5836
		 *
5837
		 * @return string Plugin slug.
5838
		 */
5839
		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...
5840
			if ( ! isset( $this->_slug ) ) {
5841
				$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
5842
				$this->_slug           = $id_slug_type_path_map[ $this->_module_id ]['slug'];
5843
			}
5844
5845
			return $this->_slug;
5846
		}
5847
5848
		/**
5849
		 * @author Vova Feldman (@svovaf)
5850
		 * @since  1.2.1.7
5851
		 *
5852
		 * @return string Plugin slug.
5853
		 */
5854
		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...
5855
			return $this->_slug . ( $this->can_use_premium_code() ? '-premium' : '' );
5856
		}
5857
5858
		/**
5859
		 * @author Vova Feldman (@svovaf)
5860
		 * @since  1.0.1
5861
		 *
5862
		 * @return number Plugin ID.
5863
		 */
5864
		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...
5865
			return $this->_plugin->id;
5866
		}
5867
5868
		/**
5869
		 * @author Vova Feldman (@svovaf)
5870
		 * @since  1.2.1.5
5871
		 *
5872
		 * @return string Freemius SDK version
5873
		 */
5874
		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...
5875
			return $this->version;
5876
		}
5877
5878
		/**
5879
		 * @author Vova Feldman (@svovaf)
5880
		 * @since  1.2.1.5
5881
		 *
5882
		 * @return number Parent plugin ID (if parent exist).
5883
		 */
5884
		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...
5885
			return $this->is_addon() ?
5886
				$this->get_parent_instance()->get_id() :
5887
				$this->_plugin->id;
5888
		}
5889
5890
		/**
5891
		 * @author Vova Feldman (@svovaf)
5892
		 * @since  1.0.1
5893
		 *
5894
		 * @return string Plugin public key.
5895
		 */
5896
		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...
5897
			return $this->_plugin->public_key;
5898
		}
5899
5900
		/**
5901
		 * Will be available only on sandbox mode.
5902
		 *
5903
		 * @author Vova Feldman (@svovaf)
5904
		 * @since  1.0.4
5905
		 *
5906
		 * @return mixed Plugin secret key.
5907
		 */
5908
		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...
5909
			return $this->_plugin->secret_key;
5910
		}
5911
5912
		/**
5913
		 * @author Vova Feldman (@svovaf)
5914
		 * @since  1.1.1
5915
		 *
5916
		 * @return bool
5917
		 */
5918
		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...
5919
			return ! empty( $this->_plugin->secret_key );
5920
		}
5921
5922
		/**
5923
		 * @author Vova Feldman (@svovaf)
5924
		 * @since  1.0.9
5925
		 *
5926
		 * @return string
5927
		 */
5928
		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...
5929
			$this->_logger->entrance();
5930
5931
			if ( ! isset( $this->_plugin_name ) ) {
5932
				$plugin_data = $this->get_plugin_data();
5933
5934
				// Get name.
5935
				$this->_plugin_name = $plugin_data['Name'];
5936
5937
				// Check if plugin name contains "(Premium)" suffix and remove it.
5938
				$suffix     = ' (premium)';
5939
				$suffix_len = strlen( $suffix );
5940
5941
				if ( strlen( $plugin_data['Name'] ) > $suffix_len &&
5942
				     $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len )
5943
				) {
5944
					$this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len );
5945
				}
5946
5947
				$this->_logger->departure( 'Name = ' . $this->_plugin_name );
5948
			}
5949
5950
			return $this->_plugin_name;
5951
		}
5952
5953
		/**
5954
		 * @author Vova Feldman (@svovaf)
5955
		 * @since  1.0.0
5956
		 *
5957
		 * @return string
5958
		 */
5959
		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...
5960
			$this->_logger->entrance();
5961
5962
			$plugin_data = $this->get_plugin_data();
5963
5964
			$this->_logger->departure( 'Version = ' . $plugin_data['Version'] );
5965
5966
			return $this->apply_filters( 'plugin_version', $plugin_data['Version'] );
5967
		}
5968
5969
		/**
5970
		 * @author Vova Feldman (@svovaf)
5971
		 * @since  1.2.1.7
5972
		 *
5973
		 * @return string
5974
		 */
5975
		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...
5976
			$this->_logger->entrance();
5977
5978
			$title = $this->_plugin->title;
5979
5980
			return $this->apply_filters( 'plugin_title', $title );
5981
		}
5982
5983
		/**
5984
		 * @author Vova Feldman (@svovaf)
5985
		 * @since 1.2.2.7
5986
		 *
5987
		 * @param bool $lowercase
5988
		 *
5989
		 * @return string
5990
		 */
5991
		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...
5992
			$label = $this->is_addon() ?
5993
				$this->get_text_inline( 'Add-On', 'addon' ) :
5994
				( $this->is_plugin() ?
5995
					$this->get_text_inline( 'Plugin', 'plugin' ) :
5996
					$this->get_text_inline( 'Theme', 'theme' ) );
5997
5998
			if ( $lowercase ) {
5999
				$label = strtolower( $label );
6000
			}
6001
6002
			return $label;
6003
		}
6004
6005
		/**
6006
		 * @author Vova Feldman (@svovaf)
6007
		 * @since  1.0.4
6008
		 *
6009
		 * @return string
6010
		 */
6011
		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...
6012
			if ( ! isset( $this->_plugin_basename ) ) {
6013
				if ( $this->is_plugin() ) {
6014
					$this->_plugin_basename = plugin_basename( $this->_plugin_main_file_path );
6015
				} else {
6016
					$this->_plugin_basename = basename( dirname( $this->_plugin_main_file_path ) );
6017
				}
6018
			}
6019
6020
			return $this->_plugin_basename;
6021
		}
6022
6023
		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...
6024
			$this->_logger->entrance();
6025
6026
			$plugin_folder = $this->_plugin_basename;
6027
6028
			while ( '.' !== dirname( $plugin_folder ) ) {
6029
				$plugin_folder = dirname( $plugin_folder );
6030
			}
6031
6032
			$this->_logger->departure( 'Folder Name = ' . $plugin_folder );
6033
6034
			return $plugin_folder;
6035
		}
6036
6037
		#endregion ------------------------------------------------------------------
6038
6039
		/* Account
6040
		------------------------------------------------------------------------------------------------------------------*/
6041
6042
		/**
6043
		 * Find plugin's slug by plugin's basename.
6044
		 *
6045
		 * @author Vova Feldman (@svovaf)
6046
		 * @since  1.0.9
6047
		 *
6048
		 * @param string $plugin_base_name
6049
		 *
6050
		 * @return false|string
6051
		 */
6052
		private static function find_slug_by_basename( $plugin_base_name ) {
6053
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
6054
6055
			if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) {
6056
				return false;
6057
			}
6058
6059
			return $file_slug_map[ $plugin_base_name ];
6060
		}
6061
6062
		/**
6063
		 * Store the map between the plugin's basename to the slug.
6064
		 *
6065
		 * @author Vova Feldman (@svovaf)
6066
		 * @since  1.0.9
6067
		 */
6068
		private function store_file_slug_map() {
6069
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
6070
6071
			if ( ! array( $file_slug_map ) ) {
6072
				$file_slug_map = array();
6073
			}
6074
6075
			if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) ||
6076
			     $file_slug_map[ $this->_plugin_basename ] !== $this->_slug
6077
			) {
6078
				$file_slug_map[ $this->_plugin_basename ] = $this->_slug;
6079
				self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true );
6080
			}
6081
		}
6082
6083
		/**
6084
		 * @return FS_User[]
6085
		 */
6086
		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...
6087
			$users = self::$_accounts->get_option( 'users', array() );
6088
6089
			if ( ! is_array( $users ) ) {
6090
				$users = array();
6091
			}
6092
6093
			return $users;
6094
		}
6095
6096
		/**
6097
		 * @param string $module_type
6098
		 *
6099
		 * @return FS_Site[]
6100
		 */
6101
		private static function get_all_sites( $module_type = WP_FS__MODULE_TYPE_PLUGIN ) {
6102
			$sites = self::get_account_option( 'sites', $module_type );
6103
6104
			if ( ! is_array( $sites ) ) {
6105
				$sites = array();
6106
			}
6107
6108
			return $sites;
6109
		}
6110
6111
		/**
6112
		 * @author Leo Fajardo (@leorw)
6113
		 *
6114
		 * @since  1.2.2
6115
		 *
6116
		 * @param string $option_name
6117
		 * @param string $module_type
6118
		 *
6119
		 * @return mixed
6120
		 */
6121
		private static function get_account_option( $option_name, $module_type ) {
6122
			if ( WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) {
6123
				$option_name = $module_type . '_' . $option_name;
6124
			}
6125
6126
			return self::$_accounts->get_option( $option_name, array() );
6127
		}
6128
6129
		/**
6130
		 * @author Leo Fajardo (@leorw)
6131
		 *
6132
		 * @since  1.2.2
6133
		 *
6134
		 * @param string $option_name
6135
		 * @param mixed  $option_value
6136
		 * @param bool   $store
6137
		 */
6138
		private function set_account_option( $option_name, $option_value, $store ) {
6139
			self::set_account_option_by_module(
6140
				$this->_module_type,
6141
				$option_name,
6142
				$option_value,
6143
				$store
6144
			);
6145
		}
6146
6147
		/**
6148
		 * @author Vova Feldman (@svovaf)
6149
		 *
6150
		 * @since  1.2.2.7
6151
		 *
6152
		 * @param string $module_type
6153
		 * @param string $option_name
6154
		 * @param mixed  $option_value
6155
		 * @param bool   $store
6156
		 */
6157
		private static function set_account_option_by_module( $module_type, $option_name, $option_value, $store ) {
6158
			if ( WP_FS__MODULE_TYPE_PLUGIN != $module_type ) {
6159
				$option_name = $module_type . '_' . $option_name;
6160
			}
6161
6162
			self::$_accounts->set_option( $option_name, $option_value, $store );
6163
		}
6164
6165
		/**
6166
		 * @author Vova Feldman (@svovaf)
6167
		 * @since  1.0.6
6168
		 *
6169
		 * @param string $module_type
6170
		 *
6171
		 * @return FS_Plugin_License[]
6172
		 */
6173
		private static function get_all_licenses( $module_type = WP_FS__MODULE_TYPE_PLUGIN ) {
6174
			$licenses = self::get_account_option( 'licenses', $module_type );
6175
6176
			if ( ! is_array( $licenses ) ) {
6177
				$licenses = array();
6178
			}
6179
6180
			return $licenses;
6181
		}
6182
6183
		/**
6184
		 * @param string|bool $module_type
6185
		 *
6186
		 * @return FS_Plugin_Plan[]
6187
		 */
6188
		private static function get_all_plans( $module_type = false ) {
6189
			$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 6188 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...
6190
6191
			if ( ! is_array( $plans ) ) {
6192
				$plans = array();
6193
			}
6194
6195
			return $plans;
6196
		}
6197
6198
		/**
6199
		 * @author Vova Feldman (@svovaf)
6200
		 * @since  1.0.4
6201
		 *
6202
		 * @return FS_Plugin_Tag[]
6203
		 */
6204
		private static function get_all_updates() {
6205
			$updates = self::$_accounts->get_option( 'updates', array() );
6206
6207
			if ( ! is_array( $updates ) ) {
6208
				$updates = array();
6209
			}
6210
6211
			return $updates;
6212
		}
6213
6214
		/**
6215
		 * @author Vova Feldman (@svovaf)
6216
		 * @since  1.0.6
6217
		 *
6218
		 * @return array<number,FS_Plugin[]>|false
6219
		 */
6220
		private static function get_all_addons() {
6221
			$addons = self::$_accounts->get_option( 'addons', array() );
6222
6223
			if ( ! is_array( $addons ) ) {
6224
				$addons = array();
6225
			}
6226
6227
			return $addons;
6228
		}
6229
6230
		/**
6231
		 * @author Vova Feldman (@svovaf)
6232
		 * @since  1.0.6
6233
		 *
6234
		 * @return FS_Plugin[]|false
6235
		 */
6236
		private static function get_all_account_addons() {
6237
			$addons = self::$_accounts->get_option( 'account_addons', array() );
6238
6239
			if ( ! is_array( $addons ) ) {
6240
				$addons = array();
6241
			}
6242
6243
			return $addons;
6244
		}
6245
6246
		/**
6247
		 * Check if user has connected his account (opted-in).
6248
		 *
6249
		 * Note:
6250
		 *      If the user opted-in and opted-out on a later stage,
6251
		 *      this will still return true. If you want to check if the
6252
		 *      user is currently opted-in, use:
6253
		 *          `$fs->is_registered() && $fs->is_tracking_allowed()`
6254
		 *
6255
		 * @author Vova Feldman (@svovaf)
6256
		 * @since  1.0.1
6257
		 * @return bool
6258
		 */
6259
		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...
6260
			return is_object( $this->_user );
6261
		}
6262
6263
		/**
6264
		 * Returns TRUE if the user opted-in and didn't disconnect (opt-out).
6265
		 *
6266
		 * @author Leo Fajardo (@leorw)
6267
		 * @since  1.2.1.5
6268
		 *
6269
		 * @return bool
6270
		 */
6271
		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...
6272
			return ( is_object( $this->_site ) && true !== $this->_site->is_disconnected );
6273
		}
6274
6275
		/**
6276
		 * @author Vova Feldman (@svovaf)
6277
		 * @since  1.0.4
6278
		 *
6279
		 * @return FS_Plugin
6280
		 */
6281
		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...
6282
			return $this->_plugin;
6283
		}
6284
6285
		/**
6286
		 * @author Vova Feldman (@svovaf)
6287
		 * @since  1.0.3
6288
		 *
6289
		 * @return FS_User
6290
		 */
6291
		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...
6292
			return $this->_user;
6293
		}
6294
6295
		/**
6296
		 * @author Vova Feldman (@svovaf)
6297
		 * @since  1.0.3
6298
		 *
6299
		 * @return FS_Site
6300
		 */
6301
		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...
6302
			return $this->_site;
6303
		}
6304
6305
		/**
6306
		 * Get plugin add-ons.
6307
		 *
6308
		 * @author Vova Feldman (@svovaf)
6309
		 * @since  1.0.6
6310
		 *
6311
		 * @since  1.1.7.3 If not yet loaded, fetch data from the API.
6312
		 *
6313
		 * @param bool $flush
6314
		 *
6315
		 * @return FS_Plugin[]|false
6316
		 */
6317
		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...
6318
			$this->_logger->entrance();
6319
6320
			if ( ! $this->_has_addons ) {
6321
				return false;
6322
			}
6323
6324
			$addons = $this->sync_addons( $flush );
6325
6326
			return ( ! is_array( $addons ) || empty( $addons ) ) ?
6327
				false :
6328
				$addons;
6329
		}
6330
6331
		/**
6332
		 * @author Vova Feldman (@svovaf)
6333
		 * @since  1.0.6
6334
		 *
6335
		 * @return FS_Plugin[]|false
6336
		 */
6337
		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...
6338
			$this->_logger->entrance();
6339
6340
			$addons = self::get_all_account_addons();
6341
6342
			if ( ! is_array( $addons ) ||
6343
			     ! isset( $addons[ $this->_plugin->id ] ) ||
6344
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
6345
			     0 === count( $addons[ $this->_plugin->id ] )
6346
			) {
6347
				return false;
6348
			}
6349
6350
			return $addons[ $this->_plugin->id ];
6351
		}
6352
6353
		/**
6354
		 * Check if user has any
6355
		 *
6356
		 * @author Vova Feldman (@svovaf)
6357
		 * @since  1.1.6
6358
		 *
6359
		 * @return bool
6360
		 */
6361
		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...
6362
			$addons = $this->get_account_addons();
6363
6364
			return is_array( $addons ) && ( 0 < count( $addons ) );
6365
		}
6366
6367
6368
		/**
6369
		 * Get add-on by ID (from local data).
6370
		 *
6371
		 * @author Vova Feldman (@svovaf)
6372
		 * @since  1.0.6
6373
		 *
6374
		 * @param number $id
6375
		 *
6376
		 * @return FS_Plugin|false
6377
		 */
6378
		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...
6379
			$this->_logger->entrance();
6380
6381
			$addons = $this->get_addons();
6382
6383
			if ( is_array( $addons ) ) {
6384
				foreach ( $addons as $addon ) {
6385
					if ( $id == $addon->id ) {
6386
						return $addon;
6387
					}
6388
				}
6389
			}
6390
6391
			return false;
6392
		}
6393
6394
		/**
6395
		 * Get add-on by slug (from local data).
6396
		 *
6397
		 * @author Vova Feldman (@svovaf)
6398
		 * @since  1.0.6
6399
		 *
6400
		 * @param string $slug
6401
		 *
6402
		 * @param bool   $flush
6403
		 *
6404
		 * @return FS_Plugin|false
6405
		 */
6406
		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...
6407
			$this->_logger->entrance();
6408
6409
			$addons = $this->get_addons( $flush );
6410
6411
			if ( is_array( $addons ) ) {
6412
				foreach ( $addons as $addon ) {
6413
					if ( $slug === $addon->slug ) {
6414
						return $addon;
6415
					}
6416
				}
6417
			}
6418
6419
			return false;
6420
		}
6421
6422
		#----------------------------------------------------------------------------------
6423
		#region Plans & Licensing
6424
		#----------------------------------------------------------------------------------
6425
6426
		/**
6427
		 * Check if running premium plugin code.
6428
		 *
6429
		 * @author Vova Feldman (@svovaf)
6430
		 * @since  1.0.5
6431
		 *
6432
		 * @return bool
6433
		 */
6434
		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...
6435
			return $this->_plugin->is_premium;
6436
		}
6437
6438
		/**
6439
		 * Get site's plan ID.
6440
		 *
6441
		 * @author Vova Feldman (@svovaf)
6442
		 * @since  1.0.2
6443
		 *
6444
		 * @return number
6445
		 */
6446
		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...
6447
			return $this->_site->plan->id;
6448
		}
6449
6450
		/**
6451
		 * Get site's plan title.
6452
		 *
6453
		 * @author Vova Feldman (@svovaf)
6454
		 * @since  1.0.2
6455
		 *
6456
		 * @return string
6457
		 */
6458
		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...
6459
			return $this->_site->plan->title;
6460
		}
6461
6462
		/**
6463
		 * @author Vova Feldman (@svovaf)
6464
		 * @since  1.0.9
6465
		 *
6466
		 * @return FS_Plugin_Plan|false
6467
		 */
6468
		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...
6469
			return is_object( $this->_site->plan ) ?
6470
				$this->_site->plan :
6471
				false;
6472
		}
6473
6474
		/**
6475
		 * @author Vova Feldman (@svovaf)
6476
		 * @since  1.0.3
6477
		 *
6478
		 * @return bool
6479
		 */
6480
		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...
6481
			$this->_logger->entrance();
6482
6483
			if ( ! $this->is_registered() ) {
6484
				return false;
6485
			}
6486
6487
			return $this->_site->is_trial();
6488
		}
6489
6490
		/**
6491
		 * Check if currently in a trial with payment method (credit card or paypal).
6492
		 *
6493
		 * @author Vova Feldman (@svovaf)
6494
		 * @since  1.1.7
6495
		 *
6496
		 * @return bool
6497
		 */
6498
		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...
6499
			$this->_logger->entrance();
6500
6501
			if ( ! $this->is_trial() ) {
6502
				return false;
6503
			}
6504
6505
			return $this->has_active_valid_license() && ( $this->_site->trial_plan_id == $this->_license->plan_id );
6506
		}
6507
6508
		/**
6509
		 * Check if trial already utilized.
6510
		 *
6511
		 * @since 1.0.9
6512
		 *
6513
		 * @return bool
6514
		 */
6515
		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...
6516
			$this->_logger->entrance();
6517
6518
			if ( ! $this->is_registered() ) {
6519
				return false;
6520
			}
6521
6522
			return $this->_site->is_trial_utilized();
6523
		}
6524
6525
		/**
6526
		 * Get trial plan information (if in trial).
6527
		 *
6528
		 * @author Vova Feldman (@svovaf)
6529
		 * @since  1.0.9
6530
		 *
6531
		 * @return bool|FS_Plugin_Plan
6532
		 */
6533
		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...
6534
			$this->_logger->entrance();
6535
6536
			if ( ! $this->is_trial() ) {
6537
				return false;
6538
			}
6539
6540
			return $this->_storage->trial_plan;
6541
		}
6542
6543
		/**
6544
		 * Check if the user has an activate, non-expired license on current plugin's install.
6545
		 *
6546
		 * @since 1.0.9
6547
		 *
6548
		 * @return bool
6549
		 */
6550
		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...
6551
			$this->_logger->entrance();
6552
6553
			if ( ! $this->is_registered() ) {
6554
				return false;
6555
			}
6556
6557
			if ( ! $this->has_paid_plan() ) {
6558
				return false;
6559
			}
6560
6561
			return (
6562
				! $this->is_trial() &&
6563
				'free' !== $this->_site->plan->name &&
6564
				$this->has_active_valid_license()
6565
			);
6566
		}
6567
6568
		/**
6569
		 * @author Vova Feldman (@svovaf)
6570
		 * @since  1.0.4
6571
		 *
6572
		 * @return bool
6573
		 */
6574
		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...
6575
			if ( ! $this->is_registered() ) {
6576
				return true;
6577
			}
6578
6579
			if ( ! $this->has_paid_plan() ) {
6580
				return true;
6581
			}
6582
6583
			return (
6584
				'free' === $this->_site->plan->name ||
6585
				! $this->has_features_enabled_license()
6586
			);
6587
		}
6588
6589
		/**
6590
		 * @author Vova Feldman (@svovaf)
6591
		 * @since  1.0.5
6592
		 *
6593
		 * @return bool
6594
		 */
6595
		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...
6596
			$this->_logger->entrance();
6597
6598
			$premium_license = $this->_get_available_premium_license();
6599
6600
			return ( false !== $premium_license );
6601
		}
6602
6603
		/**
6604
		 * Check if user has any licenses associated with the plugin (including expired or blocking).
6605
		 *
6606
		 * @author Vova Feldman (@svovaf)
6607
		 * @since  1.1.7.3
6608
		 *
6609
		 * @return bool
6610
		 */
6611
		private function has_any_license() {
6612
			return is_array( $this->_licenses ) && ( 0 < count( $this->_licenses ) );
6613
		}
6614
6615
		/**
6616
		 * @author Vova Feldman (@svovaf)
6617
		 * @since  1.0.5
6618
		 *
6619
		 * @return FS_Plugin_License|false
6620
		 */
6621
		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...
6622
			$this->_logger->entrance();
6623
6624
			if ( ! $this->has_paid_plan() ) {
6625
				return false;
6626
			}
6627
6628
			if ( is_array( $this->_licenses ) ) {
6629
				foreach ( $this->_licenses as $license ) {
6630
					if ( ! $license->is_utilized() && $license->is_features_enabled() ) {
6631
						return $license;
6632
					}
6633
				}
6634
			}
6635
6636
			return false;
6637
		}
6638
6639
		/**
6640
		 * Sync local plugin plans with remote server.
6641
		 *
6642
		 * @author Vova Feldman (@svovaf)
6643
		 * @since  1.0.5
6644
		 *
6645
		 * @return FS_Plugin_Plan[]|object
6646
		 */
6647
		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...
6648
			$plans = $this->_fetch_plugin_plans();
6649
6650
			if ( $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) {
6651
				$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...
6652
				$this->_store_plans();
6653
			}
6654
6655
			$this->do_action( 'after_plans_sync', $plans );
6656
6657
			return $this->_plans;
6658
		}
6659
6660
		/**
6661
		 * @author Vova Feldman (@svovaf)
6662
		 * @since  1.0.5
6663
		 *
6664
		 * @param number $id
6665
		 *
6666
		 * @return FS_Plugin_Plan|false
6667
		 */
6668
		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...
6669
			$this->_logger->entrance();
6670
6671
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
6672
				$this->_sync_plans();
6673
			}
6674
6675
			foreach ( $this->_plans as $plan ) {
6676
				if ( $id == $plan->id ) {
6677
					return $plan;
6678
				}
6679
			}
6680
6681
			return false;
6682
		}
6683
6684
		/**
6685
		 * @author Vova Feldman (@svovaf)
6686
		 * @since  1.1.8.1
6687
		 *
6688
		 * @param string $name
6689
		 *
6690
		 * @return FS_Plugin_Plan|false
6691
		 */
6692
		private function get_plan_by_name( $name ) {
6693
			$this->_logger->entrance();
6694
6695
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
6696
				$this->_sync_plans();
6697
			}
6698
6699
			foreach ( $this->_plans as $plan ) {
6700
				if ( $name == $plan->name ) {
6701
					return $plan;
6702
				}
6703
			}
6704
6705
			return false;
6706
		}
6707
6708
		/**
6709
		 * Sync local plugin plans with remote server.
6710
		 *
6711
		 * @author Vova Feldman (@svovaf)
6712
		 * @since  1.0.6
6713
		 *
6714
		 * @param number|bool $site_license_id
6715
		 *
6716
		 * @return FS_Plugin_License[]|object
6717
		 */
6718
		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...
6719
			$licenses = $this->_fetch_licenses( false, $site_license_id );
6720
6721
			if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) {
6722
				$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...
6723
				$this->_store_licenses();
6724
			}
6725
6726
			// Update current license.
6727
			if ( is_object( $this->_license ) ) {
6728
				$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...
6729
			}
6730
6731
			return $this->_licenses;
6732
		}
6733
6734
		/**
6735
		 * @author Vova Feldman (@svovaf)
6736
		 * @since  1.0.5
6737
		 *
6738
		 * @param number $id
6739
		 *
6740
		 * @return FS_Plugin_License|false
6741
		 */
6742
		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...
6743
			$this->_logger->entrance();
6744
6745
			if ( ! is_numeric( $id ) ) {
6746
				return false;
6747
			}
6748
6749
			if ( ! $this->has_any_license() ) {
6750
				$this->_sync_licenses();
6751
			}
6752
6753
			foreach ( $this->_licenses as $license ) {
6754
				if ( $id == $license->id ) {
6755
					return $license;
6756
				}
6757
			}
6758
6759
			return false;
6760
		}
6761
6762
		/**
6763
		 * Sync site's license with user licenses.
6764
		 *
6765
		 * @author Vova Feldman (@svovaf)
6766
		 * @since  1.0.6
6767
		 *
6768
		 * @param FS_Plugin_License|null $new_license
6769
		 */
6770
		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...
6771
			$this->_logger->entrance();
6772
6773
			$this->_license = $new_license;
6774
6775
			if ( ! is_object( $new_license ) ) {
6776
				$this->_site->license_id = null;
6777
				$this->_sync_site_subscription( null );
6778
6779
				return;
6780
			}
6781
6782
			$this->_site->license_id = $this->_license->id;
6783
6784
			if ( ! is_array( $this->_licenses ) ) {
6785
				$this->_licenses = array();
6786
			}
6787
6788
			$is_license_found = false;
6789
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
6790
				if ( $new_license->id == $this->_licenses[ $i ]->id ) {
6791
					$this->_licenses[ $i ] = $new_license;
6792
6793
					$is_license_found = true;
6794
					break;
6795
				}
6796
			}
6797
6798
			// If new license just append.
6799
			if ( ! $is_license_found ) {
6800
				$this->_licenses[] = $new_license;
6801
			}
6802
6803
			$this->_sync_site_subscription( $new_license );
6804
		}
6805
6806
		/**
6807
		 * Sync site's subscription.
6808
		 *
6809
		 * @author Vova Feldman (@svovaf)
6810
		 * @since  1.0.9
6811
		 *
6812
		 * @param FS_Plugin_License|null $license
6813
		 *
6814
		 * @return bool|\FS_Subscription
6815
		 */
6816
		private function _sync_site_subscription( $license ) {
6817
			if ( ! is_object( $license ) ) {
6818
				unset( $this->_storage->subscription );
6819
6820
				return false;
6821
			}
6822
6823
			// Load subscription details if not lifetime.
6824
			$subscription = $license->is_lifetime() ?
6825
				false :
6826
				$this->_fetch_site_license_subscription();
6827
6828
			if ( is_object( $subscription ) && ! isset( $subscription->error ) ) {
6829
				$this->_storage->subscription = $subscription;
6830
			} else {
6831
				unset( $this->_storage->subscription );
6832
			}
6833
6834
			return $subscription;
6835
		}
6836
6837
		/**
6838
		 * @author Vova Feldman (@svovaf)
6839
		 * @since  1.0.6
6840
		 *
6841
		 * @return bool|\FS_Plugin_License
6842
		 */
6843
		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...
6844
			return $this->_license;
6845
		}
6846
6847
		/**
6848
		 * @return bool|\FS_Subscription
6849
		 */
6850
		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...
6851
			return isset( $this->_storage->subscription ) ?
6852
				$this->_storage->subscription :
6853
				false;
6854
		}
6855
6856
		/**
6857
		 * @author Vova Feldman (@svovaf)
6858
		 * @since  1.0.2
6859
		 *
6860
		 * @param string $plan  Plan name
6861
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
6862
		 *
6863
		 * @return bool
6864
		 */
6865
		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...
6866
			$this->_logger->entrance();
6867
6868
			if ( ! $this->is_registered() ) {
6869
				return false;
6870
			}
6871
6872
			$plan = strtolower( $plan );
6873
6874
			if ( $this->_site->plan->name === $plan ) // Exact plan.
6875
			{
6876
				return true;
6877
			} else if ( $exact ) // Required exact, but plans are different.
6878
			{
6879
				return false;
6880
			}
6881
6882
			$current_plan_order  = - 1;
6883
			$required_plan_order = - 1;
6884
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
6885
				if ( $plan === $this->_plans[ $i ]->name ) {
6886
					$required_plan_order = $i;
6887
				} else if ( $this->_site->plan->name === $this->_plans[ $i ]->name ) {
6888
					$current_plan_order = $i;
6889
				}
6890
			}
6891
6892
			return ( $current_plan_order > $required_plan_order );
6893
		}
6894
6895
		/**
6896
		 * Check if module has only one plan.
6897
		 *
6898
		 * @author Vova Feldman (@svovaf)
6899
		 * @since  1.2.1.7
6900
		 *
6901
		 * @return bool
6902
		 */
6903
		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...
6904
			$this->_logger->entrance();
6905
6906
			if ( ! $this->is_registered() ||
6907
			     ! is_array( $this->_plans ) ||
6908
			     0 === count( $this->_plans )
6909
			) {
6910
				return true;
6911
			}
6912
6913
			return ( 1 === count( $this->_plans ) );
6914
		}
6915
6916
		/**
6917
		 * Check if plan based on trial. If not in trial mode, should return false.
6918
		 *
6919
		 * @since  1.0.9
6920
		 *
6921
		 * @param string $plan  Plan name
6922
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
6923
		 *
6924
		 * @return bool
6925
		 */
6926
		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...
6927
			$this->_logger->entrance();
6928
6929
			if ( ! $this->is_registered() ) {
6930
				return false;
6931
			}
6932
6933
			if ( ! $this->is_trial() ) {
6934
				return false;
6935
			}
6936
6937
			if ( ! isset( $this->_storage->trial_plan ) ) {
6938
				// Store trial plan information.
6939
				$this->_enrich_site_trial_plan( true );
6940
			}
6941
6942
			if ( $this->_storage->trial_plan->name === $plan ) // Exact plan.
6943
			{
6944
				return true;
6945
			} else if ( $exact ) // Required exact, but plans are different.
6946
			{
6947
				return false;
6948
			}
6949
6950
			$current_plan_order  = - 1;
6951
			$required_plan_order = - 1;
6952
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
6953
				if ( $plan === $this->_plans[ $i ]->name ) {
6954
					$required_plan_order = $i;
6955
				} else if ( $this->_storage->trial_plan->name === $this->_plans[ $i ]->name ) {
6956
					$current_plan_order = $i;
6957
				}
6958
			}
6959
6960
			return ( $current_plan_order > $required_plan_order );
6961
		}
6962
6963
		/**
6964
		 * Check if plugin has any paid plans.
6965
		 *
6966
		 * @author Vova Feldman (@svovaf)
6967
		 * @since  1.0.7
6968
		 *
6969
		 * @return bool
6970
		 */
6971
		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...
6972
			return $this->_has_paid_plans ||
6973
			       FS_Plan_Manager::instance()->has_paid_plan( $this->_plans );
6974
		}
6975
6976
		/**
6977
		 * Check if plugin has any plan with a trail.
6978
		 *
6979
		 * @author Vova Feldman (@svovaf)
6980
		 * @since  1.0.9
6981
		 *
6982
		 * @return bool
6983
		 */
6984
		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...
6985
			if ( ! $this->is_registered() ) {
6986
				/**
6987
				 * @author Vova Feldman(@svovaf)
6988
				 * @since  1.2.1.5
6989
				 *
6990
				 * Allow setting a trial from the SDK without calling the API.
6991
				 * But, if the user did opt-in, continue using the real data from the API.
6992
				 */
6993
				if ( $this->_trial_days >= 0 ) {
6994
					return true;
6995
				}
6996
6997
				return false;
6998
			}
6999
7000
			return $this->_storage->get( 'has_trial_plan', false );
7001
		}
7002
7003
		/**
7004
		 * Check if plugin has any free plan, or is it premium only.
7005
		 *
7006
		 * Note: If no plans configured, assume plugin is free.
7007
		 *
7008
		 * @author Vova Feldman (@svovaf)
7009
		 * @since  1.0.7
7010
		 *
7011
		 * @return bool
7012
		 */
7013
		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...
7014
			return ! $this->is_only_premium();
7015
		}
7016
7017
		/**
7018
		 * Displays a license activation dialog box when the user clicks on the "Activate License"
7019
		 * or "Change License" link on the plugins
7020
		 * page.
7021
		 *
7022
		 * @author Leo Fajardo (@leorw)
7023
		 * @since  1.1.9
7024
		 */
7025
		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...
7026
			$vars = array(
7027
				'id' => $this->_module_id,
7028
			);
7029
7030
			fs_require_template( 'forms/license-activation.php', $vars );
7031
			fs_require_template( 'forms/resend-key.php', $vars );
7032
		}
7033
7034
		/**
7035
		 * Displays the opt-out dialog box when the user clicks on the "Opt Out" link on the "Plugins"
7036
		 * page.
7037
		 *
7038
		 * @author Leo Fajardo (@leorw)
7039
		 * @since  1.2.1.5
7040
		 */
7041
		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...
7042
			if ( $this->is_theme() ) {
7043
				$vars = null;
7044
				fs_require_once_template( '/js/jquery.content-change.php', $vars );
7045
			}
7046
7047
			$vars = array( 'id' => $this->_module_id );
7048
			fs_require_template( 'forms/optout.php', $vars );
7049
		}
7050
7051
		/**
7052
		 * Prepare page to include all required UI and logic for the license activation dialog.
7053
		 *
7054
		 * @author Vova Feldman (@svovaf)
7055
		 * @since  1.2.0
7056
		 */
7057
		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...
7058
			if ( ! $this->is_user_admin() ) {
7059
				// Only admins can activate a license.
7060
				return;
7061
			}
7062
7063
			if ( ! $this->has_paid_plan() ) {
7064
				// Module doesn't have any paid plans.
7065
				return;
7066
			}
7067
7068
			if ( ! $this->is_premium() ) {
7069
				// Only add license activation logic to the premium version.
7070
				return;
7071
			}
7072
7073
			// Add license activation link and AJAX request handler.
7074
			if ( self::is_plugins_page() ) {
7075
				/**
7076
				 * @since 1.2.0 Add license action link only on plugins page.
7077
				 */
7078
				$this->_add_license_action_link();
7079
			}
7080
7081
			// Add license activation AJAX callback.
7082
			$this->add_ajax_action( 'activate_license', array( &$this, '_activate_license_ajax_action' ) );
7083
7084
			// Add resend license AJAX callback.
7085
			$this->add_ajax_action( 'resend_license_key', array( &$this, '_resend_license_key_ajax_action' ) );
7086
		}
7087
7088
		/**
7089
		 * @author Leo Fajardo (@leorw)
7090
		 * @since  1.1.9
7091
		 */
7092
		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...
7093
			$this->_logger->entrance();
7094
7095
			$this->check_ajax_referer( 'activate_license' );
7096
7097
			$license_key = trim( fs_request_get( 'license_key' ) );
7098
7099
			if ( empty( $license_key ) ) {
7100
				exit;
7101
			}
7102
7103
			$plugin_id = fs_request_get( 'module_id', '', 'post' );
7104
			$fs        = ( $plugin_id == $this->_module_id ) ?
7105
				$this :
7106
				$this->get_addon_instance( $plugin_id );
7107
7108
			$error     = false;
7109
			$next_page = false;
7110
7111
			if ( $fs->is_registered() ) {
7112
				$api     = $fs->get_api_site_scope();
7113
				$install = $api->call( '/', 'put', array(
7114
					'license_key' => $fs->apply_filters( 'license_key', $license_key )
7115
				) );
7116
7117
				if ( isset( $install->error ) ) {
7118
					$error = $install->error->message;
7119
				} else {
7120
                    $fs->_sync_license( true );
7121
7122
                    $next_page = $fs->is_addon() ?
7123
                        $fs->get_parent_instance()->get_account_url() :
7124
                        $fs->get_account_url();
7125
7126
                    $fs->reconnect_locally();
7127
				}
7128
			} else {
7129
				$next_page = $fs->opt_in( false, false, false, $license_key );
7130
7131
				if ( isset( $next_page->error ) ) {
7132
					$error = $next_page->error;
7133
				}
7134
			}
7135
7136
			$result = array(
7137
				'success' => ( false === $error )
7138
			);
7139
7140
			if ( false !== $error ) {
7141
				$result['error'] = $error;
7142
			} else {
7143
				$result['next_page'] = $next_page;
7144
			}
7145
7146
			echo json_encode( $result );
7147
7148
			exit;
7149
		}
7150
7151
		/**
7152
		 * Billing update AJAX callback.
7153
		 *
7154
		 * @author Vova Feldman (@svovaf)
7155
		 * @since  1.2.1.5
7156
		 */
7157
		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...
7158
			$this->_logger->entrance();
7159
7160
			$this->check_ajax_referer( 'update_billing' );
7161
7162
			if ( ! $this->is_user_admin() ) {
7163
				// Only for admins.
7164
				self::shoot_ajax_failure();
7165
			}
7166
7167
			$billing = fs_request_get( 'billing' );
7168
7169
			$api    = $this->get_api_user_scope();
7170
			$result = $api->call( '/billing.json', 'put', array_merge( $billing, array(
7171
				'plugin_id' => $this->get_parent_id(),
7172
			) ) );
7173
7174
			if ( ! $this->is_api_result_entity( $result ) ) {
7175
				self::shoot_ajax_failure();
7176
			}
7177
7178
			// Purge cached billing.
7179
			$this->get_api_user_scope()->purge_cache( 'billing.json' );
7180
7181
			self::shoot_ajax_success();
7182
		}
7183
7184
		/**
7185
		 * Trial start for anonymous users (AJAX callback).
7186
		 *
7187
		 * @author Vova Feldman (@svovaf)
7188
		 * @since  1.2.1.5
7189
		 */
7190
		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...
7191
			$this->_logger->entrance();
7192
7193
			$this->check_ajax_referer( 'start_trial' );
7194
7195
			if ( ! $this->is_user_admin() ) {
7196
				// Only for admins.
7197
				self::shoot_ajax_failure();
7198
			}
7199
7200
			$trial_data = fs_request_get( 'trial' );
7201
7202
			$next_page = $this->opt_in(
7203
				false,
7204
				false,
7205
				false,
7206
				false,
7207
				false,
7208
				$trial_data['plan_id']
7209
			);
7210
7211
			if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) {
7212
				self::shoot_ajax_failure(
7213
					isset( $next_page->error ) ?
7214
						$next_page->error->message :
7215
						var_export( $next_page, true )
7216
				);
7217
			}
7218
7219
			$this->shoot_ajax_success( array(
7220
				'next_page' => $next_page,
7221
			) );
7222
		}
7223
7224
		/**
7225
		 * @author Leo Fajardo (@leorw)
7226
		 * @since  1.2.0
7227
		 */
7228
		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...
7229
			$this->_logger->entrance();
7230
7231
			$this->check_ajax_referer( 'resend_license_key' );
7232
7233
			$email_address = sanitize_email( trim( fs_request_get( 'email', '', 'post' ) ) );
7234
7235
			if ( empty( $email_address ) ) {
7236
				exit;
7237
			}
7238
7239
			$error = false;
7240
7241
			$api    = $this->get_api_plugin_scope();
7242
			$result = $api->call( '/licenses/resend.json', 'post',
7243
				array(
7244
					'email' => $email_address,
7245
					'url'   => home_url(),
7246
				)
7247
			);
7248
7249
			if ( is_object( $result ) && isset( $result->error ) ) {
7250
				$error = $result->error;
7251
7252
				if ( in_array( $error->code, array( 'invalid_email', 'no_user' ) ) ) {
7253
					$error = $this->get_text_inline( "We couldn't find your email address in the system, are you sure it's the right address?", 'email-not-found' );
7254
				} else if ( 'no_license' === $error->code ) {
7255
					$error = $this->get_text_inline( "We can't see any active licenses associated with that email address, are you sure it's the right address?", 'no-active-licenses' );
7256
				} else {
7257
					$error = $error->message;
7258
				}
7259
			}
7260
7261
			$licenses = array(
7262
				'success' => ( false === $error )
7263
			);
7264
7265
			if ( false !== $error ) {
7266
				$licenses['error'] = sprintf( '%s... %s', $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ), strtolower( $error ) );
7267
			}
7268
7269
			echo json_encode( $licenses );
7270
7271
			exit;
7272
		}
7273
7274
		/**
7275
		 * @author Vova Feldman (@svovaf)
7276
		 * @since  1.2.1.8
7277
		 *
7278
		 * @var string
7279
		 */
7280
		private static $_pagenow;
7281
7282
		/**
7283
		 * Get current page or the referer if executing a WP AJAX request.
7284
		 *
7285
		 * @author Vova Feldman (@svovaf)
7286
		 * @since  1.2.1.8
7287
		 *
7288
		 * @return string
7289
		 */
7290
		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...
7291
			if ( ! isset( self::$_pagenow ) ) {
7292
				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...
7293
7294
				self::$_pagenow = $pagenow;
7295
7296
				if ( self::is_ajax() &&
7297
				     'admin-ajax.php' === $pagenow
7298
				) {
7299
					$referer = fs_get_raw_referer();
7300
7301
					if ( is_string( $referer ) ) {
7302
						$parts = explode( '?', $referer );
7303
7304
						self::$_pagenow = basename( $parts[0] );
7305
					}
7306
				}
7307
			}
7308
7309
			return self::$_pagenow;
7310
		}
7311
7312
		/**
7313
		 * Helper method to check if user in the plugins page.
7314
		 *
7315
		 * @author Vova Feldman (@svovaf)
7316
		 * @since  1.2.1.5
7317
		 *
7318
		 * @return bool
7319
		 */
7320
		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...
7321
			return ( 'plugins.php' === self::get_current_page() );
7322
		}
7323
7324
		/**
7325
		 * Helper method to check if user in the themes page.
7326
		 *
7327
		 * @author Vova Feldman (@svovaf)
7328
		 * @since  1.2.2.6
7329
		 *
7330
		 * @return bool
7331
		 */
7332
		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...
7333
			return ( 'themes.php' === self::get_current_page() );
7334
		}
7335
7336
		#----------------------------------------------------------------------------------
7337
		#region Affiliation
7338
		#----------------------------------------------------------------------------------
7339
7340
        /**
7341
         * @author Leo Fajardo
7342
         * @since 1.2.3
7343
         *
7344
         * @return bool
7345
         */
7346
        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...
7347
            if ( ! is_object( $this->_plugin ) ) {
7348
                return false;
7349
            }
7350
7351
		    return $this->_plugin->has_affiliate_program();
7352
        }
7353
7354
        /**
7355
         * @author Leo Fajardo (@leorw)
7356
         * @since 1.2.4
7357
         */
7358
        private function fetch_affiliate_terms() {
7359
            if ( ! is_object( $this->plugin_affiliate_terms ) ) {
7360
                $plugins_api     = $this->get_api_plugin_scope();
7361
                $affiliate_terms = $plugins_api->get( '/aff.json?type=affiliation', false, WP_FS__TIME_WEEK_IN_SEC );
7362
7363
                if ( ! $this->is_api_result_entity( $affiliate_terms ) ) {
7364
                    return;
7365
                }
7366
7367
                $this->plugin_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms );
7368
            }
7369
        }
7370
7371
        /**
7372
         * @author Leo Fajardo (@leorw)
7373
         * @since 1.2.4
7374
         */
7375
        private function fetch_affiliate_and_custom_terms() {
7376
            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...
7377
                $application_data = $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 __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...
7378
                $flush            = ( ! isset( $application_data['status'] ) || 'pending' === $application_data['status'] );
7379
7380
                $users_api = $this->get_api_user_scope();
7381
                $result    = $users_api->get( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json", $flush, WP_FS__TIME_WEEK_IN_SEC );
7382
                if ( $this->is_api_result_object( $result, 'affiliates' ) ) {
7383
                    if ( ! empty( $result->affiliates ) ) {
7384
                        $affiliate = new FS_Affiliate( $result->affiliates[0] );
7385
7386
                        if ( ! isset( $application_data['status'] ) || $application_data['status'] !== $affiliate->status ) {
7387
                            $application_data['status']                 = $affiliate->status;
7388
                            $this->_storage->affiliate_application_data = $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...
7389
                        }
7390
7391
                        if ( $affiliate->is_using_custom_terms ) {
7392
                            $affiliate_terms = $users_api->get( "/plugins/{$this->_plugin->id}/affiliates/{$affiliate->id}/aff/{$affiliate->custom_affiliate_terms_id}.json", $flush, WP_FS__TIME_WEEK_IN_SEC );
7393
                            if ( $this->is_api_result_entity( $affiliate_terms ) ) {
7394
                                $this->custom_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms );
7395
                            }
7396
                        }
7397
7398
                        $this->affiliate = $affiliate;
7399
                    }
7400
                }
7401
            }
7402
        }
7403
7404
        /**
7405
         * @author Leo Fajardo (@leorw)
7406
         * @since 1.2.3
7407
         */
7408
        private function fetch_affiliate_and_terms() {
7409
            $this->_logger->entrance();
7410
7411
            $this->fetch_affiliate_terms();
7412
            $this->fetch_affiliate_and_custom_terms();
7413
        }
7414
7415
        /**
7416
         * @author Leo Fajardo
7417
         * @since 1.2.3
7418
         *
7419
         * @return FS_Affiliate
7420
         */
7421
        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...
7422
            return $this->affiliate;
7423
        }
7424
7425
7426
        /**
7427
         * @author Leo Fajardo
7428
         * @since 1.2.3
7429
         *
7430
         * @return FS_AffiliateTerms
7431
         */
7432
        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...
7433
            return is_object( $this->custom_affiliate_terms ) ?
7434
                $this->custom_affiliate_terms :
7435
                $this->plugin_affiliate_terms;
7436
        }
7437
7438
        /**
7439
         * @author Leo Fajardo
7440
         * @since 1.2.3
7441
         *
7442
         * @return FS_Affiliate|null
7443
         */
7444
        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...
7445
            $this->_logger->entrance();
7446
7447
            $this->check_ajax_referer( 'submit_affiliate_application' );
7448
7449
            if ( ! $this->is_user_admin() ) {
7450
                // Only for admins.
7451
                self::shoot_ajax_failure();
7452
            }
7453
7454
            $affiliate = fs_request_get( 'affiliate' );
7455
7456
            if ( empty( $affiliate['promotion_methods'] ) ) {
7457
                unset( $affiliate['promotion_methods'] );
7458
            }
7459
7460
            if ( ! empty( $affiliate['additional_domains'] ) ) {
7461
                $affiliate['additional_domains'] = array_unique( $affiliate['additional_domains'] );
7462
            }
7463
7464
            if ( ! $this->is_registered() ) {
7465
                // Opt in but don't track usage.
7466
                $next_page = $this->opt_in(
7467
                    false,
7468
                    false,
7469
                    false,
7470
                    false,
7471
                    false,
7472
                    false,
7473
                    true
7474
                );
7475
7476
                if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) {
7477
                    self::shoot_ajax_failure(
7478
                        isset( $next_page->error ) ?
7479
                            $next_page->error->message :
7480
                            var_export( $next_page, true )
7481
                    );
7482
                } else if ( $this->is_pending_activation() ) {
7483
                    self::shoot_ajax_failure( $this->get_text_inline( 'Account is pending activation.', 'account-is-pending-activation' ) );
7484
                }
7485
            }
7486
7487
            $this->fetch_affiliate_terms();
7488
7489
            $api    = $this->get_api_user_scope();
7490
            $result = $api->call(
7491
                ( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json" ),
7492
                'post',
7493
                $affiliate
7494
            );
7495
7496
            if ( $this->is_api_error( $result ) ) {
7497
                self::shoot_ajax_failure(
7498
                    isset( $result->error ) ?
7499
                        $result->error->message :
7500
                        var_export( $result, true )
7501
                );
7502
            }
7503
            else
7504
            {
7505
                if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) {
7506
                    $this->_admin_notices->remove_sticky( 'affiliate_program' );
7507
                }
7508
7509
                $affiliate_application_data = array(
7510
                    'status'                       => 'pending',
7511
                    'stats_description'            => $affiliate['stats_description'],
7512
                    'promotion_method_description' => $affiliate['promotion_method_description'],
7513
                );
7514
7515
                if ( ! empty( $affiliate['promotion_methods'] ) ) {
7516
                    $affiliate_application_data['promotion_methods'] = $affiliate['promotion_methods'];
7517
                }
7518
7519
                if ( ! empty( $affiliate['domain'] ) ) {
7520
                    $affiliate_application_data['domain'] = $affiliate['domain'];
7521
                }
7522
7523
                if ( ! empty( $affiliate['additional_domains'] ) ) {
7524
                    $affiliate_application_data['additional_domains'] = $affiliate['additional_domains'];
7525
                }
7526
7527
                $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...
7528
            }
7529
7530
            // Purge cached affiliate.
7531
            $api->purge_cache( 'affiliate.json' );
7532
7533
            self::shoot_ajax_success( $result );
7534
        }
7535
7536
        /**
7537
         * @author Leo Fajardo
7538
         * @since 1.2.3
7539
         *
7540
         * @return array|null
7541
         */
7542
        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...
7543
            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...
7544
                return null;
7545
            }
7546
7547
            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...
7548
        }
7549
7550
        #endregion Affiliation ------------------------------------------------------------
7551
7552
		#----------------------------------------------------------------------------------
7553
		#region URL Generators
7554
		#----------------------------------------------------------------------------------
7555
7556
		/**
7557
		 * Alias to pricing_url().
7558
		 *
7559
		 * @author Vova Feldman (@svovaf)
7560
		 * @since  1.0.2
7561
		 *
7562
		 * @uses   pricing_url()
7563
		 *
7564
		 * @param string $period Billing cycle
7565
		 * @param bool   $is_trial
7566
		 *
7567
		 * @return string
7568
		 */
7569
		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...
7570
			return $this->pricing_url( $period, $is_trial );
7571
		}
7572
7573
		/**
7574
		 * @author Vova Feldman (@svovaf)
7575
		 * @since  1.0.9
7576
		 *
7577
		 * @uses   get_upgrade_url()
7578
		 *
7579
		 * @return string
7580
		 */
7581
		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...
7582
			return $this->get_upgrade_url( WP_FS__PERIOD_ANNUALLY, true );
7583
		}
7584
7585
		/**
7586
		 * Plugin's pricing URL.
7587
		 *
7588
		 * @author Vova Feldman (@svovaf)
7589
		 * @since  1.0.4
7590
		 *
7591
		 * @param string $billing_cycle Billing cycle
7592
		 *
7593
		 * @param bool   $is_trial
7594
		 *
7595
		 * @return string
7596
		 */
7597
		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...
7598
			$this->_logger->entrance();
7599
7600
			$params = array(
7601
				'billing_cycle' => $billing_cycle
7602
			);
7603
7604
			if ( $is_trial ) {
7605
				$params['trial'] = 'true';
7606
			}
7607
7608
			return $this->_get_admin_page_url( 'pricing', $params );
7609
		}
7610
7611
		/**
7612
		 * Checkout page URL.
7613
		 *
7614
		 * @author   Vova Feldman (@svovaf)
7615
		 * @since    1.0.6
7616
		 *
7617
		 * @param string $billing_cycle Billing cycle
7618
		 * @param bool   $is_trial
7619
		 * @param array  $extra         (optional) Extra parameters, override other query params.
7620
		 *
7621
		 * @return string
7622
		 */
7623
		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...
7624
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
7625
			$is_trial = false,
7626
			$extra = array()
7627
		) {
7628
			$this->_logger->entrance();
7629
7630
			$params = array(
7631
				'checkout'      => 'true',
7632
				'billing_cycle' => $billing_cycle,
7633
			);
7634
7635
			if ( $is_trial ) {
7636
				$params['trial'] = 'true';
7637
			}
7638
7639
			/**
7640
			 * Params in extra override other params.
7641
			 */
7642
			$params = array_merge( $params, $extra );
7643
7644
			return $this->_get_admin_page_url( 'pricing', $params );
7645
		}
7646
7647
		/**
7648
		 * Add-on checkout URL.
7649
		 *
7650
		 * @author   Vova Feldman (@svovaf)
7651
		 * @since    1.1.7
7652
		 *
7653
		 * @param number $addon_id
7654
		 * @param number $pricing_id
7655
		 * @param string $billing_cycle
7656
		 * @param bool   $is_trial
7657
		 *
7658
		 * @return string
7659
		 */
7660
		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...
7661
			$addon_id,
7662
			$pricing_id,
7663
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
7664
			$is_trial = false
7665
		) {
7666
			return $this->checkout_url( $billing_cycle, $is_trial, array(
7667
				'plugin_id'  => $addon_id,
7668
				'pricing_id' => $pricing_id,
7669
			) );
7670
		}
7671
7672
		#endregion
7673
7674
		#endregion ------------------------------------------------------------------
7675
7676
		/**
7677
		 * Check if plugin has any add-ons.
7678
		 *
7679
		 * @author Vova Feldman (@svovaf)
7680
		 * @since  1.0.5
7681
		 *
7682
		 * @since  1.1.7.3 Base logic only on the parameter provided by the developer in the init function.
7683
		 *
7684
		 * @return bool
7685
		 */
7686
		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...
7687
			$this->_logger->entrance();
7688
7689
			return $this->_has_addons;
7690
		}
7691
7692
		/**
7693
		 * Check if plugin can work in anonymous mode.
7694
		 *
7695
		 * @author     Vova Feldman (@svovaf)
7696
		 * @since      1.0.9
7697
		 *
7698
		 * @return bool
7699
		 *
7700
		 * @deprecated Please use is_enable_anonymous() instead
7701
		 */
7702
		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...
7703
			return $this->_enable_anonymous;
7704
		}
7705
7706
		/**
7707
		 * Check if plugin can work in anonymous mode.
7708
		 *
7709
		 * @author Vova Feldman (@svovaf)
7710
		 * @since  1.1.9
7711
		 *
7712
		 * @return bool
7713
		 */
7714
		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...
7715
			return $this->_enable_anonymous;
7716
		}
7717
7718
		/**
7719
		 * Check if plugin is premium only (no free plans).
7720
		 *
7721
		 * @author Vova Feldman (@svovaf)
7722
		 * @since  1.1.9
7723
		 *
7724
		 * @return bool
7725
		 */
7726
		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...
7727
			return $this->_is_premium_only;
7728
		}
7729
7730
		/**
7731
		 * Checks if the plugin's type is "plugin". The other type is "theme".
7732
		 *
7733
		 * @author Leo Fajardo (@leorw)
7734
		 * @since  1.2.2
7735
		 *
7736
		 * @return bool
7737
		 */
7738
		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...
7739
			return ( WP_FS__MODULE_TYPE_PLUGIN === $this->_module_type );
7740
		}
7741
7742
		/**
7743
		 * @author Leo Fajardo (@leorw)
7744
		 * @since  1.2.2
7745
		 *
7746
		 * @return string
7747
		 */
7748
		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...
7749
			if ( ! isset( $this->_module_type ) ) {
7750
				$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
7751
				$this->_module_type    = $id_slug_type_path_map[ $this->_module_id ]['type'];
7752
			}
7753
7754
			return $this->_module_type;
7755
		}
7756
7757
		/**
7758
		 * @author Leo Fajardo (@leorw)
7759
		 * @since  1.2.2
7760
		 *
7761
		 * @return string
7762
		 */
7763
		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...
7764
			return $this->_plugin_main_file_path;
7765
		}
7766
7767
		/**
7768
		 * Check if module has a premium code version.
7769
		 *
7770
		 * Serviceware module might be freemium without any
7771
		 * premium code version, where the paid features
7772
		 * are all part of the service.
7773
		 *
7774
		 * @author Vova Feldman (@svovaf)
7775
		 * @since  1.2.1.6
7776
		 *
7777
		 * @return bool
7778
		 */
7779
		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...
7780
			return $this->_has_premium_version;
7781
		}
7782
7783
		/**
7784
		 * Check if feature supported with current site's plan.
7785
		 *
7786
		 * @author Vova Feldman (@svovaf)
7787
		 * @since  1.0.1
7788
		 *
7789
		 * @todo   IMPLEMENT
7790
		 *
7791
		 * @param number $feature_id
7792
		 *
7793
		 * @throws Exception
7794
		 */
7795
		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...
7796
			throw new Exception( 'not implemented' );
7797
		}
7798
7799
		/**
7800
		 * @author Vova Feldman (@svovaf)
7801
		 * @since  1.0.1
7802
		 *
7803
		 * @return bool Is running in SSL/HTTPS
7804
		 */
7805
		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...
7806
			return WP_FS__IS_HTTPS;
7807
		}
7808
7809
		/**
7810
		 * @author Vova Feldman (@svovaf)
7811
		 * @since  1.0.9
7812
		 *
7813
		 * @return bool Is running in AJAX call.
7814
		 *
7815
		 * @link   http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax
7816
		 */
7817
		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...
7818
			return ( defined( 'DOING_AJAX' ) && DOING_AJAX );
7819
		}
7820
7821
		/**
7822
		 * Check if it's an AJAX call targeted for the current module.
7823
		 *
7824
		 * @author Vova Feldman (@svovaf)
7825
		 * @since  1.2.0
7826
		 *
7827
		 * @param array|string $actions Collection of AJAX actions.
7828
		 *
7829
		 * @return bool
7830
		 */
7831
		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...
7832
			// Verify it's an ajax call.
7833
			if ( ! self::is_ajax() ) {
7834
				return false;
7835
			}
7836
7837
			// Verify the call is relevant for the plugin.
7838
			if ( $this->_module_id != fs_request_get( 'module_id' ) ) {
7839
				return false;
7840
			}
7841
7842
			// Verify it's one of the specified actions.
7843
			if ( is_string( $actions ) ) {
7844
				$actions = explode( ',', $actions );
7845
			}
7846
7847
			if ( is_array( $actions ) && 0 < count( $actions ) ) {
7848
				$ajax_action = fs_request_get( 'action' );
7849
7850
				foreach ( $actions as $action ) {
7851
					if ( $ajax_action === $this->get_action_tag( $action ) ) {
7852
						return true;
7853
					}
7854
				}
7855
			}
7856
7857
			return false;
7858
		}
7859
7860
		/**
7861
		 * Check if it's an AJAX call targeted for current request.
7862
		 *
7863
		 * @author Vova Feldman (@svovaf)
7864
		 * @since  1.2.0
7865
		 *
7866
		 * @param array|string $actions Collection of AJAX actions.
7867
		 * @param number|null  $module_id
7868
		 *
7869
		 * @return bool
7870
		 */
7871
		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...
7872
			// Verify it's an ajax call.
7873
			if ( ! self::is_ajax() ) {
7874
				return false;
7875
			}
7876
7877
7878
			if ( ! empty( $module_id ) ) {
7879
				// Verify the call is relevant for the plugin.
7880
				if ( $module_id != fs_request_get( 'module_id' ) ) {
7881
					return false;
7882
				}
7883
			}
7884
7885
			// Verify it's one of the specified actions.
7886
			if ( is_string( $actions ) ) {
7887
				$actions = explode( ',', $actions );
7888
			}
7889
7890
			if ( is_array( $actions ) && 0 < count( $actions ) ) {
7891
				$ajax_action = fs_request_get( 'action' );
7892
7893
				foreach ( $actions as $action ) {
7894
					if ( $ajax_action === self::get_ajax_action_static( $action, $module_id ) ) {
7895
						return true;
7896
					}
7897
				}
7898
			}
7899
7900
			return false;
7901
		}
7902
7903
		/**
7904
		 * @author Vova Feldman (@svovaf)
7905
		 * @since  1.1.7
7906
		 *
7907
		 * @return bool
7908
		 */
7909
		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...
7910
			return ( defined( 'DOING_CRON' ) && DOING_CRON );
7911
		}
7912
7913
		/**
7914
		 * Check if a real user is visiting the admin dashboard.
7915
		 *
7916
		 * @author Vova Feldman (@svovaf)
7917
		 * @since  1.1.7
7918
		 *
7919
		 * @return bool
7920
		 */
7921
		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...
7922
			return is_admin() && ! self::is_ajax() && ! self::is_cron();
7923
		}
7924
7925
		/**
7926
		 * Check if a real user is in the customizer view.
7927
		 *
7928
		 * @author Vova Feldman (@svovaf)
7929
		 * @since  1.2.2.7
7930
		 *
7931
		 * @return bool
7932
		 */
7933
		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...
7934
			return is_customize_preview();
7935
		}
7936
7937
		/**
7938
		 * Check if running in HTTPS and if site's plan matching the specified plan.
7939
		 *
7940
		 * @param string $plan
7941
		 * @param bool   $exact
7942
		 *
7943
		 * @return bool
7944
		 */
7945
		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...
7946
			return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) );
7947
		}
7948
7949
		/**
7950
		 * Construct plugin's settings page URL.
7951
		 *
7952
		 * @author Vova Feldman (@svovaf)
7953
		 * @since  1.0.4
7954
		 *
7955
		 * @param string $page
7956
		 * @param array  $params
7957
		 *
7958
		 * @return string
7959
		 */
7960
		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...
7961
			if ( 0 < count( $params ) ) {
7962
				foreach ( $params as $k => $v ) {
7963
					$params[ $k ] = urlencode( $v );
7964
				}
7965
			}
7966
7967
			$page_param = $this->_menu->get_slug( $page );
7968
7969
            if ( empty( $page ) &&
7970
                $this->is_theme() &&
7971
                // Show the opt-in as an overlay for free wp.org themes or themes without any settings page.
7972
                ( $this->is_free_wp_org_theme() || ! $this->has_settings_menu() ) ) {
7973
                    $params[ $this->get_unique_affix() . '_show_optin' ] = 'true';
7974
7975
                    return add_query_arg(
7976
                        $params,
7977
                        admin_url( 'themes.php' )
7978
                    );
7979
            }
7980
7981
			if ( ! $this->has_settings_menu() ) {
7982
				if ( ! empty( $page ) ) {
7983
					// Module doesn't have a setting page, but since the request is for
7984
					// a specific Freemius page, use the admin.php path.
7985
					return add_query_arg( array_merge( $params, array(
7986
						'page' => $page_param,
7987
					) ), admin_url( 'admin.php' ) );
7988
				} else {
7989
					if ( $this->is_activation_mode() ) {
7990
						/**
7991
						 * @author Vova Feldman
7992
						 * @since  1.2.1.6
7993
						 *
7994
						 * If plugin doesn't have a settings page, create one for the opt-in screen.
7995
						 */
7996
						return add_query_arg( array_merge( $params, array(
7997
							'page' => $this->_slug,
7998
						) ), admin_url( 'admin.php', 'admin' ) );
7999
					} else {
8000
						// Plugin without a settings page.
8001
                        return add_query_arg(
8002
                            $params,
8003
                            admin_url( 'plugins.php' )
8004
                        );
8005
					}
8006
				}
8007
			}
8008
8009
			// Module has a submenu settings page.
8010
			if ( ! $this->_menu->is_top_level() ) {
8011
				$parent_slug = $this->_menu->get_parent_slug();
8012
				$menu_file   = ( false !== strpos( $parent_slug, '.php' ) ) ?
8013
					$parent_slug :
8014
					'admin.php';
8015
8016
				return add_query_arg( array_merge( $params, array(
8017
					'page' => $page_param,
8018
				) ), admin_url( $menu_file, 'admin' ) );
8019
			}
8020
8021
			// Module has a top level CPT settings page.
8022
			if ( $this->_menu->is_cpt() ) {
8023
				if ( empty( $page ) && $this->is_activation_mode() ) {
8024
					return add_query_arg( array_merge( $params, array(
8025
						'page' => $page_param
8026
					) ), admin_url( 'admin.php', 'admin' ) );
8027
				} else {
8028
					if ( ! empty( $page ) ) {
8029
						$params['page'] = $page_param;
8030
					}
8031
8032
					return add_query_arg(
8033
						$params,
8034
						admin_url( $this->_menu->get_raw_slug(), 'admin' )
8035
					);
8036
				}
8037
			}
8038
8039
			// Module has a custom top level settings page.
8040
			return add_query_arg( array_merge( $params, array(
8041
				'page' => $page_param,
8042
			) ), admin_url( 'admin.php', 'admin' ) );
8043
		}
8044
8045
		/**
8046
		 * Check if currently in a specified admin page.
8047
		 *
8048
		 * @author Vova Feldman (@svovaf)
8049
		 * @since  1.2.2.7
8050
		 *
8051
		 * @param string $page
8052
		 *
8053
		 * @return bool
8054
		 */
8055
		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...
8056
			return ( $this->_menu->get_slug( $page ) === fs_request_get( 'page', '', 'get' ) );
8057
		}
8058
8059
		/**
8060
		 * Get module's main admin setting page URL.
8061
		 *
8062
		 * @author Vova Feldman (@svovaf)
8063
		 * @since  1.2.2.7
8064
		 *
8065
		 * @return string
8066
		 */
8067
		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...
8068
			return $this->_menu->main_menu_url();
8069
		}
8070
8071
		/**
8072
		 * Check if currently on the theme's setting page or
8073
		 * on any of the Freemius added pages (via tabs).
8074
		 *
8075
		 * @author Vova Feldman (@svovaf)
8076
		 * @since  1.2.2.7
8077
		 *
8078
		 * @return bool
8079
		 */
8080
		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...
8081
			return fs_starts_with(
8082
				fs_request_get( 'page', '', 'get' ),
8083
				$this->_menu->get_slug()
8084
			);
8085
		}
8086
8087
		/**
8088
		 * Plugin's account page + sync license URL.
8089
		 *
8090
		 * @author Vova Feldman (@svovaf)
8091
		 * @since  1.1.9.1
8092
		 *
8093
		 * @param bool|number $plugin_id
8094
		 * @param bool        $add_action_nonce
8095
		 * @param array       $params
8096
		 *
8097
		 * @return string
8098
		 */
8099
		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...
8100
			if ( is_numeric( $plugin_id ) ) {
8101
				$params['plugin_id'] = $plugin_id;
8102
			}
8103
8104
			return $this->get_account_url(
8105
				$this->get_unique_affix() . '_sync_license',
8106
				$params,
8107
				$add_action_nonce
8108
			);
8109
		}
8110
8111
		/**
8112
		 * Plugin's account URL.
8113
		 *
8114
		 * @author Vova Feldman (@svovaf)
8115
		 * @since  1.0.4
8116
		 *
8117
		 * @param bool|string $action
8118
		 * @param array       $params
8119
		 *
8120
		 * @param bool        $add_action_nonce
8121
		 *
8122
		 * @return string
8123
		 */
8124
		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...
8125
			if ( is_string( $action ) ) {
8126
				$params['fs_action'] = $action;
8127
			}
8128
8129
			self::require_pluggable_essentials();
8130
8131
			return ( $add_action_nonce && is_string( $action ) ) ?
8132
				fs_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) :
8133
				$this->_get_admin_page_url( 'account', $params );
8134
		}
8135
8136
		/**
8137
		 * @author  Vova Feldman (@svovaf)
8138
		 * @since   1.2.0
8139
		 *
8140
		 * @param string $tab
8141
		 * @param bool   $action
8142
		 * @param array  $params
8143
		 * @param bool   $add_action_nonce
8144
		 *
8145
		 * @return string
8146
		 *
8147
		 * @uses    get_account_url()
8148
		 */
8149
		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...
8150
			$params['tab'] = $tab;
8151
8152
			return $this->get_account_url( $action, $params, $add_action_nonce );
8153
		}
8154
8155
		/**
8156
		 * Plugin's account URL.
8157
		 *
8158
		 * @author Vova Feldman (@svovaf)
8159
		 * @since  1.0.4
8160
		 *
8161
		 * @param bool|string $topic
8162
		 * @param bool|string $message
8163
		 *
8164
		 * @return string
8165
		 */
8166
		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...
8167
			$params = array();
8168
			if ( is_string( $topic ) ) {
8169
				$params['topic'] = $topic;
8170
			}
8171
			if ( is_string( $message ) ) {
8172
				$params['message'] = $message;
8173
			}
8174
8175
			if ( $this->is_addon() ) {
8176
				$params['addon_id'] = $this->get_id();
8177
8178
				return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params );
8179
			} else {
8180
				return $this->_get_admin_page_url( 'contact', $params );
8181
			}
8182
		}
8183
8184
		/**
8185
		 * Add-on direct info URL.
8186
		 *
8187
		 * @author Vova Feldman (@svovaf)
8188
		 * @since  1.1.0
8189
		 *
8190
		 * @param string $slug
8191
		 *
8192
		 * @return string
8193
		 */
8194
		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...
8195
			return $this->_get_admin_page_url( 'addons', array(
8196
				'slug' => $slug
8197
			) );
8198
		}
8199
8200
		/* Logger
8201
		------------------------------------------------------------------------------------------------------------------*/
8202
		/**
8203
		 * @param string $id
8204
		 * @param bool   $prefix_slug
8205
		 *
8206
		 * @return FS_Logger
8207
		 */
8208
		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...
8209
			return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id );
8210
		}
8211
8212
		/**
8213
		 * @param      $id
8214
		 * @param bool $load_options
8215
		 * @param bool $prefix_slug
8216
		 *
8217
		 * @return FS_Option_Manager
8218
		 */
8219
		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...
8220
			return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options );
8221
		}
8222
8223
		/* Security
8224
		------------------------------------------------------------------------------------------------------------------*/
8225
		private static function _encrypt( $str ) {
8226
			if ( is_null( $str ) ) {
8227
				return null;
8228
			}
8229
8230
			/**
8231
			 * The encrypt/decrypt functions are used to protect
8232
			 * the user from messing up with some of the sensitive
8233
			 * data stored for the module as a JSON in the database.
8234
			 *
8235
			 * I used the same suggested hack by the theme review team.
8236
			 * For more details, look at the function `Base64UrlDecode()`
8237
			 * in `./sdk/FreemiusBase.php`.
8238
			 *
8239
			 * @todo   Remove this hack once the base64 error is removed from the Theme Check.
8240
			 *
8241
			 * @author Vova Feldman (@svovaf)
8242
			 * @since  1.2.2
8243
			 */
8244
			$fn = 'base64' . '_encode';
8245
8246
			return $fn( $str );
8247
		}
8248
8249
		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...
8250
			if ( is_null( $str ) ) {
8251
				return null;
8252
			}
8253
8254
			/**
8255
			 * The encrypt/decrypt functions are used to protect
8256
			 * the user from messing up with some of the sensitive
8257
			 * data stored for the module as a JSON in the database.
8258
			 *
8259
			 * I used the same suggested hack by the theme review team.
8260
			 * For more details, look at the function `Base64UrlDecode()`
8261
			 * in `./sdk/FreemiusBase.php`.
8262
			 *
8263
			 * @todo   Remove this hack once the base64 error is removed from the Theme Check.
8264
			 *
8265
			 * @author Vova Feldman (@svovaf)
8266
			 * @since  1.2.2
8267
			 */
8268
			$fn = 'base64' . '_decode';
8269
8270
			return $fn( $str );
8271
		}
8272
8273
		/**
8274
		 * @author Vova Feldman (@svovaf)
8275
		 * @since  1.0.5
8276
		 *
8277
		 * @param FS_Entity $entity
8278
		 *
8279
		 * @return FS_Entity Return an encrypted clone entity.
8280
		 */
8281
		private static function _encrypt_entity( FS_Entity $entity ) {
8282
			$clone = clone $entity;
8283
			$props = get_object_vars( $entity );
8284
8285
			foreach ( $props as $key => $val ) {
8286
				$clone->{$key} = self::_encrypt( $val );
8287
			}
8288
8289
			return $clone;
8290
		}
8291
8292
		/**
8293
		 * @author Vova Feldman (@svovaf)
8294
		 * @since  1.0.5
8295
		 *
8296
		 * @param FS_Entity $entity
8297
		 *
8298
		 * @return FS_Entity Return an decrypted clone entity.
8299
		 */
8300
		private static function decrypt_entity( FS_Entity $entity ) {
8301
			$clone = clone $entity;
8302
			$props = get_object_vars( $entity );
8303
8304
			foreach ( $props as $key => $val ) {
8305
				$clone->{$key} = self::_decrypt( $val );
8306
			}
8307
8308
			return $clone;
8309
		}
8310
8311
		/**
8312
		 * Tries to activate account based on POST params.
8313
		 *
8314
		 * @author Vova Feldman (@svovaf)
8315
		 * @since  1.0.2
8316
		 */
8317
		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...
8318
			if ( $this->is_registered() ) {
8319
				// Already activated.
8320
				return;
8321
			}
8322
8323
			self::_clean_admin_content_section();
8324
8325
			if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) {
8326
//				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...
8327
8328
				// Verify matching plugin details.
8329
				if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) {
8330
					return;
8331
				}
8332
8333
				$user              = new FS_User();
8334
				$user->id          = fs_request_get( 'user_id' );
8335
				$user->public_key  = fs_request_get( 'user_public_key' );
8336
				$user->secret_key  = fs_request_get( 'user_secret_key' );
8337
				$user->email       = fs_request_get( 'user_email' );
8338
				$user->first       = fs_request_get( 'user_first' );
8339
				$user->last        = fs_request_get( 'user_last' );
8340
				$user->is_verified = fs_request_get_bool( 'user_is_verified' );
8341
8342
				$site              = new FS_Site();
8343
				$site->id          = fs_request_get( 'install_id' );
8344
				$site->public_key  = fs_request_get( 'install_public_key' );
8345
				$site->secret_key  = fs_request_get( 'install_secret_key' );
8346
				$site->plan->id    = fs_request_get( 'plan_id' );
8347
				$site->plan->title = fs_request_get( 'plan_title' );
8348
				$site->plan->name  = fs_request_get( 'plan_name' );
8349
8350
				$plans      = array();
8351
				$plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) );
8352
				foreach ( $plans_data as $p ) {
8353
					$plans[] = new FS_Plugin_Plan( $p );
8354
				}
8355
8356
				$this->_set_account( $user, $site, $plans );
8357
8358
				// Reload the page with the keys.
8359
				fs_redirect( $this->_get_admin_page_url() );
8360
			}
8361
		}
8362
8363
		/**
8364
		 * @author Vova Feldman (@svovaf)
8365
		 * @since  1.0.7
8366
		 *
8367
		 * @param string $email
8368
		 *
8369
		 * @return FS_User|bool
8370
		 */
8371
		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...
8372
			self::$_static_logger->entrance();
8373
8374
			$email = trim( strtolower( $email ) );
8375
			$users = self::get_all_users();
8376
			if ( is_array( $users ) ) {
8377
				foreach ( $users as $u ) {
8378
					if ( $email === trim( strtolower( $u->email ) ) ) {
8379
						return $u;
8380
					}
8381
				}
8382
			}
8383
8384
			return false;
8385
		}
8386
8387
		#----------------------------------------------------------------------------------
8388
		#region Account (Loading, Updates & Activation)
8389
		#----------------------------------------------------------------------------------
8390
8391
		/***
8392
		 * Load account information (user + site).
8393
		 *
8394
		 * @author Vova Feldman (@svovaf)
8395
		 * @since  1.0.1
8396
		 */
8397
		private function _load_account() {
8398
			$this->_logger->entrance();
8399
8400
			$this->do_action( 'before_account_load' );
8401
8402
			$sites    = self::get_all_sites( $this->_module_type );
8403
			$users    = self::get_all_users();
8404
			$plans    = self::get_all_plans( $this->_module_type );
8405
			$licenses = self::get_all_licenses( $this->_module_type );
8406
8407
			if ( $this->_logger->is_on() && is_admin() ) {
8408
				$this->_logger->log( 'sites = ' . var_export( $sites, true ) );
8409
				$this->_logger->log( 'users = ' . var_export( $users, true ) );
8410
				$this->_logger->log( 'plans = ' . var_export( $plans, true ) );
8411
				$this->_logger->log( 'licenses = ' . var_export( $licenses, true ) );
8412
			}
8413
8414
			$site = isset( $sites[ $this->_slug ] ) ? $sites[ $this->_slug ] : false;
8415
8416
			if ( is_object( $site ) &&
8417
			     is_numeric( $site->id ) &&
8418
			     is_numeric( $site->user_id ) &&
8419
			     is_object( $site->plan )
8420
			) {
8421
				// Load site.
8422
				$this->_site       = clone $site;
8423
				$this->_site->plan = self::decrypt_entity( $this->_site->plan );
8424
8425
                /**
8426
                 * If the install owner's details are not stored locally, use the previous user's details if available.
8427
                 *
8428
                 * @author Leo Fajardo (@leorw)
8429
                 */
8430
				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...
8431
                    $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...
8432
                } else {
8433
                    $user_id = $this->_site->user_id;
8434
                }
8435
8436
				// Load relevant user.
8437
				$this->_user = clone $users[ $user_id ];
8438
8439
				// Load plans.
8440
				$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...
8441
				if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) {
8442
					$this->_sync_plans();
8443
				} else {
8444
					for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
8445
						if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) {
8446
							$this->_plans[ $i ] = self::decrypt_entity( $this->_plans[ $i ] );
8447
						} else {
8448
							unset( $this->_plans[ $i ] );
8449
						}
8450
					}
8451
				}
8452
8453
				// Load licenses.
8454
				$this->_licenses = array();
8455
				if ( is_array( $licenses ) &&
8456
				     isset( $licenses[ $this->_slug ] ) &&
8457
				     isset( $licenses[ $this->_slug ][ $this->_user->id ] )
8458
				) {
8459
					$this->_licenses = $licenses[ $this->_slug ][ $this->_user->id ];
8460
				}
8461
8462
				$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...
8463
8464
				if ( $this->_site->version != $this->get_plugin_version() ) {
8465
					// If stored install version is different than current installed plugin version,
8466
					// then update plugin version event.
8467
					$this->update_plugin_version_event();
8468
				}
8469
			}
8470
8471
			$this->_register_account_hooks();
8472
		}
8473
8474
		/**
8475
		 * @author Vova Feldman (@svovaf)
8476
		 * @since  1.0.1
8477
		 *
8478
		 * @param FS_User    $user
8479
		 * @param FS_Site    $site
8480
		 * @param bool|array $plans
8481
		 */
8482
		private function _set_account( FS_User $user, FS_Site $site, $plans = false ) {
8483
			$site->slug    = $this->_slug;
8484
			$site->user_id = $user->id;
8485
8486
			$this->_site = $site;
8487
			$this->_user = $user;
8488
			if ( false !== $plans ) {
8489
				$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...
8490
			}
8491
8492
			$this->send_install_update();
8493
8494
			$this->_store_account();
8495
8496
		}
8497
8498
		/**
8499
		 * @author Vova Feldman (@svovaf)
8500
		 * @since  1.1.7.4
8501
		 *
8502
		 * @param array $override_with
8503
		 *
8504
		 * @return array
8505
		 */
8506
		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...
8507
			$this->_logger->entrance();
8508
8509
			$current_user = self::_get_current_wp_user();
8510
8511
			$activation_action = $this->get_unique_affix() . '_activate_new';
8512
			$return_url        = $this->is_anonymous() ?
8513
				// If skipped already, then return to the account page.
8514
				$this->get_account_url( $activation_action, array(), false ) :
8515
				// Return to the module's main page.
8516
				$this->get_after_activation_url( 'after_connect_url', array( 'fs_action' => $activation_action ) );
8517
8518
			$params = array(
8519
				'user_firstname'               => $current_user->user_firstname,
8520
				'user_lastname'                => $current_user->user_lastname,
8521
				'user_nickname'                => $current_user->user_nicename,
8522
				'user_email'                   => $current_user->user_email,
8523
				'user_ip'                      => WP_FS__REMOTE_ADDR,
8524
				'plugin_slug'                  => $this->_slug,
8525
				'plugin_id'                    => $this->get_id(),
8526
				'plugin_public_key'            => $this->get_public_key(),
8527
				'plugin_version'               => $this->get_plugin_version(),
8528
				'return_url'                   => fs_nonce_url( $return_url, $activation_action ),
8529
				'account_url'                  => fs_nonce_url( $this->_get_admin_page_url(
8530
					'account',
8531
					array( 'fs_action' => 'sync_user' )
8532
				), 'sync_user' ),
8533
				'site_uid'                     => $this->get_anonymous_id(),
8534
				'site_url'                     => get_site_url(),
8535
				'site_name'                    => get_bloginfo( 'name' ),
8536
				'platform_version'             => get_bloginfo( 'version' ),
8537
				'sdk_version'                  => $this->version,
8538
				'programming_language_version' => phpversion(),
8539
				'language'                     => get_bloginfo( 'language' ),
8540
				'charset'                      => get_bloginfo( 'charset' ),
8541
				'is_premium'                   => $this->is_premium(),
8542
				'is_active'                    => true,
8543
				'is_uninstalled'               => false,
8544
			);
8545
8546
			if ( $this->is_pending_activation() &&
8547
			     ! 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...
8548
			) {
8549
				$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...
8550
			}
8551
8552
			if ( WP_FS__SKIP_EMAIL_ACTIVATION && $this->has_secret_key() ) {
8553
				// Even though rand() is known for its security issues,
8554
				// the timestamp adds another layer of protection.
8555
				// It would be very hard for an attacker to get the secret key form here.
8556
				// Plus, this should never run in production since the secret should never
8557
				// be included in the production version.
8558
				$params['ts']     = WP_FS__SCRIPT_START_TIME;
8559
				$params['salt']   = md5( uniqid( rand() ) );
8560
				$params['secure'] = md5(
8561
					$params['ts'] .
8562
					$params['salt'] .
8563
					$this->get_secret_key()
8564
				);
8565
			}
8566
8567
			return array_merge( $params, $override_with );
8568
		}
8569
8570
		/**
8571
		 * 1. If successful opt-in or pending activation returns the next page that the user should be redirected to.
8572
		 * 2. If there was an API error, return the API result.
8573
		 *
8574
		 * @author Vova Feldman (@svovaf)
8575
		 * @since  1.1.7.4
8576
		 *
8577
		 * @param string|bool $email
8578
		 * @param string|bool $first
8579
		 * @param string|bool $last
8580
		 * @param string|bool $license_key
8581
		 * @param bool        $is_uninstall       If "true", this means that the module is currently being uninstalled.
8582
		 *                                        In this case, the user and site info will be sent to the server but no
8583
		 *                                        data will be saved to the WP installation's database.
8584
		 * @param number|bool $trial_plan_id
8585
		 * @param bool        $is_disconnected Whether or not to opt in without tracking.
8586
		 *
8587
		 * @return string|object
8588
		 * @use    WP_Error
8589
		 */
8590
		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...
8591
			$email = false,
8592
			$first = false,
8593
			$last = false,
8594
			$license_key = false,
8595
			$is_uninstall = false,
8596
			$trial_plan_id = false,
8597
            $is_disconnected = false
8598
		) {
8599
			$this->_logger->entrance();
8600
8601
			if ( false === $email ) {
8602
				$current_user = self::_get_current_wp_user();
8603
				$email        = $current_user->user_email;
8604
			}
8605
8606
			/**
8607
			 * @since 1.2.1 If activating with license key, ignore the context-user
8608
			 *              since the user will be automatically loaded from the license.
8609
			 */
8610
			if ( empty( $license_key ) ) {
8611
				// Clean up pending license if opt-ing in again.
8612
				$this->_storage->remove( 'pending_license_key' );
8613
8614
				if ( ! $is_uninstall ) {
8615
					$fs_user = Freemius::_get_user_by_email( $email );
8616
					if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) {
8617
						return $this->install_with_current_user( false, $trial_plan_id );
8618
					}
8619
				}
8620
			}
8621
8622
			$user_info = array();
8623
			if ( ! empty( $email ) ) {
8624
				$user_info['user_email'] = $email;
8625
			}
8626
			if ( ! empty( $first ) ) {
8627
				$user_info['user_firstname'] = $first;
8628
			}
8629
			if ( ! empty( $last ) ) {
8630
				$user_info['user_lastname'] = $last;
8631
			}
8632
8633
			$params = $this->get_opt_in_params( $user_info );
8634
8635
			$filtered_license_key = false;
8636
			if ( is_string( $license_key ) ) {
8637
				$filtered_license_key  = $this->apply_filters( 'license_key', $license_key );
8638
				$params['license_key'] = $filtered_license_key;
8639
			} else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) {
8640
				$params['trial_plan_id'] = $trial_plan_id;
8641
			}
8642
8643
			if ( $is_uninstall ) {
8644
				$params['uninstall_params'] = array(
8645
					'reason_id'   => $this->_storage->uninstall_reason->id,
8646
					'reason_info' => $this->_storage->uninstall_reason->info
8647
				);
8648
			}
8649
8650
			if ( isset( $params['license_key'] ) ) {
8651
				$fs_user = Freemius::_get_user_by_email( $email );
8652
8653
				if ( is_object( $fs_user ) ) {
8654
					/**
8655
					 * If opting in with a context license and the context WP Admin user already opted in
8656
					 * before from the current site, add the user context security params to avoid the
8657
					 * unnecessry email activation when the context license is owned by the same context user.
8658
					 * 
8659
					 * @author Leo Fajardo (@leorw)
8660
					 * @since 1.2.3
8661
					 */
8662
					$params = array_merge( $params, FS_Security::instance()->get_context_params(
8663
						$fs_user,
8664
						false,
8665
						'install_with_existing_user'
8666
					) );
8667
				}
8668
			}
8669
8670
            $params['is_disconnected'] = $is_disconnected;
8671
			$params['format']          = 'json';
8672
8673
			$url = WP_FS__ADDRESS . '/action/service/user/install/';
8674
			if ( isset( $_COOKIE['XDEBUG_SESSION'] ) ) {
8675
				$url = add_query_arg( 'XDEBUG_SESSION', 'PHPSTORM', $url );
8676
			}
8677
8678
			$response = wp_remote_post( $url, array(
8679
				'method'  => 'POST',
8680
				'body'    => $params,
8681
				'timeout' => 15,
8682
			) );
8683
8684
			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...
8685
				if ( 'https://' === substr( $url, 0, 8 ) &&
8686
				     isset( $response->errors ) &&
8687
				     isset( $response->errors['http_request_failed'] )
8688
				) {
8689
					$http_error = strtolower( $response->errors['http_request_failed'][0] );
8690
8691
					if ( false !== strpos( $http_error, 'ssl' ) ) {
8692
						// Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare).
8693
						$url = 'http://' . substr( $url, 8 );
8694
8695
						$response = wp_remote_post( $url, array(
8696
							'method'  => 'POST',
8697
							'body'    => $params,
8698
							'timeout' => 15,
8699
						) );
8700
					}
8701
				}
8702
			}
8703
8704
			if ( is_wp_error( $response ) ) {
8705
				/**
8706
				 * @var WP_Error $response
8707
				 */
8708
				$result = new stdClass();
8709
8710
				$error_code = $response->get_error_code();
8711
				$error_type = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $error_code ) ) );
8712
8713
				$result->error = (object) array(
8714
					'type'    => $error_type,
8715
					'message' => $response->get_error_message(),
8716
					'code'    => $error_code,
8717
					'http'    => 402
8718
				);
8719
8720
				return $result;
8721
			}
8722
8723
			// Module is being uninstalled, don't handle the returned data.
8724
			if ( $is_uninstall ) {
8725
				return true;
8726
			}
8727
8728
            /**
8729
             * When json_decode() executed on PHP 5.2 with an invalid JSON, it will throw a PHP warning. Unfortunately, the new Theme Check doesn't allow PHP silencing and the theme review team isn't open to change that, therefore, instead of using `@json_decode()` we had to use the method without the `@` directive.
8730
             *
8731
             * @author Vova Feldman (@svovaf)
8732
             * @since  1.2.3
8733
             * @link   https://themes.trac.wordpress.org/ticket/46134#comment:5
8734
             * @link   https://themes.trac.wordpress.org/ticket/46134#comment:9
8735
             * @link   https://themes.trac.wordpress.org/ticket/46134#comment:12
8736
             * @link   https://themes.trac.wordpress.org/ticket/46134#comment:14
8737
             */
8738
			$decoded = is_string( $response['body'] ) ?
8739
                json_decode( $response['body'] ) :
8740
                null;
8741
8742
			if ( empty( $decoded ) ) {
8743
				return false;
8744
			}
8745
8746
			if ( ! $this->is_api_result_object( $decoded ) ) {
8747
				if ( ! empty( $params['license_key'] ) ) {
8748
					// Pass the fully entered license key to the failure handler.
8749
					$params['license_key'] = $license_key;
8750
				}
8751
8752
				return $is_uninstall ?
8753
					$decoded :
8754
					$this->apply_filters( 'after_install_failure', $decoded, $params );
8755
			} else if ( isset( $decoded->pending_activation ) && $decoded->pending_activation ) {
8756
				// Pending activation, add message.
8757
				return $this->set_pending_confirmation(
8758
                    ( isset( $decoded->email ) ?
8759
                        $decoded->email :
8760
                        true ),
8761
					false,
8762
					$filtered_license_key,
8763
					! empty( $params['trial_plan_id'] )
8764
				);
8765
			} else if ( isset( $decoded->install_secret_key ) ) {
8766
				return $this->install_with_new_user(
8767
					$decoded->user_id,
8768
					$decoded->user_public_key,
8769
					$decoded->user_secret_key,
8770
					$decoded->install_id,
8771
					$decoded->install_public_key,
8772
					$decoded->install_secret_key,
8773
					false
8774
				);
8775
			}
8776
8777
			return $decoded;
8778
		}
8779
8780
		/**
8781
		 * Set user and site identities.
8782
		 *
8783
		 * @author Vova Feldman (@svovaf)
8784
		 * @since  1.0.9
8785
		 *
8786
		 * @param FS_User $user
8787
		 * @param FS_Site $site
8788
		 * @param bool    $redirect
8789
		 * @param bool    $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will
8790
		 *                              redirect (or return a URL) to the account page with a special parameter to
8791
		 *                              trigger the auto installation processes.
8792
		 *
8793
		 * @return string If redirect is `false`, returns the next page the user should be redirected to.
8794
		 */
8795
		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...
8796
			FS_User $user,
8797
			FS_Site $site,
8798
			$redirect = true,
8799
			$auto_install = false
8800
		) {
8801
			$this->_user = $user;
8802
			$this->_site = $site;
8803
8804
			$this->_sync_plans();
8805
8806
			$this->_enrich_site_plan( false );
8807
8808
			$this->_set_account( $user, $site );
8809
8810
			if ( $this->is_trial() ) {
8811
				// Store trial plan information.
8812
				$this->_enrich_site_trial_plan( true );
8813
			}
8814
8815
			// If Freemius was OFF before, turn it on.
8816
			$this->turn_on();
8817
8818
			$this->do_action( 'after_account_connection', $user, $site );
8819
8820
			if ( is_numeric( $site->license_id ) ) {
8821
				$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...
8822
			}
8823
8824
			$this->_admin_notices->remove_sticky( 'connect_account' );
8825
8826
			if ( $this->is_pending_activation() || ! $this->has_settings_menu() ) {
8827
				// Remove pending activation sticky notice (if still exist).
8828
				$this->_admin_notices->remove_sticky( 'activation_pending' );
8829
8830
				// Remove plugin from pending activation mode.
8831
				unset( $this->_storage->is_pending_activation );
8832
8833
				if ( ! $this->is_paying_or_trial() ) {
8834
					$this->_admin_notices->add_sticky(
8835
						sprintf( $this->get_text_x_inline( '%s activation was successfully completed.',
8836
							'pluginX activation was successfully...', 'plugin-x-activation-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
8837
						'activation_complete'
8838
					);
8839
				}
8840
			}
8841
8842
			if ( $this->is_paying_or_trial() ) {
8843
				if ( ! $this->is_premium() || ! $this->has_premium_version() || ! $this->has_settings_menu() ) {
8844
					if ( $this->is_paying() ) {
8845
						$this->_admin_notices->add_sticky(
8846
							sprintf(
8847
								$this->get_text_inline( 'Your account was successfully activated with the %s plan.', 'activation-with-plan-x-message' ),
8848
								$this->_site->plan->title
8849
							) . $this->get_complete_upgrade_instructions(),
8850
							'plan_upgraded',
8851
							$this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!'
8852
						);
8853
					} else {
8854
						$this->_admin_notices->add_sticky(
8855
							sprintf(
8856
								$this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ),
8857
								'<i>' . $this->get_plugin_name() . '</i>'
8858
							) . $this->get_complete_upgrade_instructions( $this->_storage->trial_plan->title ),
8859
							'trial_started',
8860
							$this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!'
8861
						);
8862
					}
8863
				}
8864
8865
				$this->_admin_notices->remove_sticky( array(
8866
					'trial_promotion',
8867
				) );
8868
			}
8869
8870
			$plugin_id = fs_request_get( 'plugin_id', false );
8871
8872
			// Store activation time ONLY for plugins (not add-ons).
8873
			if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) {
8874
				$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
8875
			}
8876
8877
			$next_page = '';
8878
8879
			$extra = array();
8880
			if ( $auto_install ) {
8881
				$extra['auto_install'] = 'true';
8882
			}
8883
8884
			if ( is_numeric( $plugin_id ) ) {
8885
				/**
8886
				 * @author Leo Fajardo
8887
				 * @since  1.2.1.6
8888
				 *
8889
				 * Also sync the license after an anonymous user subscribes.
8890
				 */
8891
				if ( $this->is_anonymous() || $plugin_id != $this->_plugin->id ) {
8892
					// Add-on was installed - sync license right after install.
8893
					$next_page = $this->_get_sync_license_url( $plugin_id, true, $extra );
8894
				}
8895
			} else {
8896
				/**
8897
				 * @author Vova Feldman (@svovaf)
8898
				 * @since  1.1.9 If site installed with a valid license, sync license.
8899
				 */
8900
				if ( $this->is_paying() ) {
8901
					$this->_sync_plugin_license( true );
8902
				}
8903
8904
				// Reload the page with the keys.
8905
				$next_page = $this->is_anonymous() ?
8906
					// If user previously skipped, redirect to account page.
8907
					$this->get_account_url( false, $extra ) :
8908
					$this->get_after_activation_url( 'after_connect_url' );
8909
			}
8910
8911
			if ( ! empty( $next_page ) && $redirect ) {
8912
				fs_redirect( $next_page );
8913
			}
8914
8915
			return $next_page;
8916
		}
8917
8918
		/**
8919
		 * Install plugin with new user information after approval.
8920
		 *
8921
		 * @author Vova Feldman (@svovaf)
8922
		 * @since  1.0.7
8923
		 */
8924
		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...
8925
			$this->_logger->entrance();
8926
8927
			if ( $this->is_registered() ) {
8928
				return;
8929
			}
8930
8931
			if ( ( $this->is_plugin() && fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) ||
8932
				// @todo This logic should be improved because it's executed on every load of a theme.
8933
			     $this->is_theme()
8934
			) {
8935
//				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...
8936
8937
				if ( fs_request_has( 'user_secret_key' ) ) {
8938
					$this->install_with_new_user(
8939
						fs_request_get( 'user_id' ),
8940
						fs_request_get( 'user_public_key' ),
8941
						fs_request_get( 'user_secret_key' ),
8942
						fs_request_get( 'install_id' ),
8943
						fs_request_get( 'install_public_key' ),
8944
						fs_request_get( 'install_secret_key' ),
8945
						true,
8946
						fs_request_get_bool( 'auto_install' )
8947
					);
8948
				} else if ( fs_request_has( 'pending_activation' ) ) {
8949
					$this->set_pending_confirmation( fs_request_get( 'user_email' ), true );
8950
				}
8951
			}
8952
		}
8953
8954
		/**
8955
		 * Install plugin with new user.
8956
		 *
8957
		 * @author Vova Feldman (@svovaf)
8958
		 * @since  1.1.7.4
8959
		 *
8960
		 * @param number $user_id
8961
		 * @param string $user_public_key
8962
		 * @param string $user_secret_key
8963
		 * @param number $install_id
8964
		 * @param string $install_public_key
8965
		 * @param string $install_secret_key
8966
		 * @param bool   $redirect
8967
		 * @param bool   $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will
8968
		 *                             redirect (or return a URL) to the account page with a special parameter to
8969
		 *                             trigger the auto installation processes.
8970
		 *
8971
		 * @return string If redirect is `false`, returns the next page the user should be redirected to.
8972
		 */
8973
		private function install_with_new_user(
8974
			$user_id,
8975
			$user_public_key,
8976
			$user_secret_key,
8977
			$install_id,
8978
			$install_public_key,
8979
			$install_secret_key,
8980
			$redirect = true,
8981
			$auto_install = false
8982
		) {
8983
			$user             = new FS_User();
8984
			$user->id         = $user_id;
8985
			$user->public_key = $user_public_key;
8986
			$user->secret_key = $user_secret_key;
8987
8988
			$this->_user = $user;
8989
			$user_result = $this->get_api_user_scope()->get();
8990
			$user        = new FS_User( $user_result );
8991
			$this->_user = $user;
8992
8993
			$site             = new FS_Site();
8994
			$site->id         = $install_id;
8995
			$site->public_key = $install_public_key;
8996
			$site->secret_key = $install_secret_key;
8997
8998
			$this->_site = $site;
8999
			$site_result = $this->get_api_site_scope()->get();
9000
			$site        = new FS_Site( $site_result );
9001
			$this->_site = $site;
9002
9003
			return $this->setup_account(
9004
				$this->_user,
9005
				$this->_site,
9006
				$redirect,
9007
				$auto_install
9008
			);
9009
		}
9010
9011
		/**
9012
		 * @author Vova Feldman (@svovaf)
9013
		 * @since  1.1.7.4
9014
		 *
9015
		 * @param string|bool $email
9016
		 * @param bool        $redirect
9017
		 * @param string|bool $license_key      Since 1.2.1.5
9018
		 * @param bool        $is_pending_trial Since 1.2.1.5
9019
		 *
9020
		 * @return string Since 1.2.1.5 if $redirect is `false`, return the pending activation page.
9021
		 */
9022
		private function set_pending_confirmation(
9023
			$email = false,
9024
			$redirect = true,
9025
			$license_key = false,
9026
			$is_pending_trial = false
9027
		) {
9028
			if ( $this->_ignore_pending_mode ) {
9029
				/**
9030
				 * If explicitly asked to ignore pending mode, set to anonymous mode
9031
				 * if require confirmation before finalizing the opt-in.
9032
				 *
9033
				 * @author Vova Feldman
9034
				 * @since  1.2.1.6
9035
				 */
9036
				$this->skip_connection();
9037
			} else {
9038
				// Install must be activated via email since
9039
				// user with the same email already exist.
9040
				$this->_storage->is_pending_activation = true;
9041
				$this->_add_pending_activation_notice( $email, $is_pending_trial );
9042
			}
9043
9044
			if ( ! empty( $license_key ) ) {
9045
				$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...
9046
			}
9047
9048
			// Remove the opt-in sticky notice.
9049
			$this->_admin_notices->remove_sticky( array(
9050
				'connect_account',
9051
				'trial_promotion',
9052
			) );
9053
9054
			$next_page = $this->get_after_activation_url( 'after_pending_connect_url' );
9055
9056
			// Reload the page with with pending activation message.
9057
			if ( $redirect ) {
9058
				fs_redirect( $next_page );
9059
			}
9060
9061
			return $next_page;
9062
		}
9063
9064
		/**
9065
		 * Install plugin with current logged WP user info.
9066
		 *
9067
		 * @author Vova Feldman (@svovaf)
9068
		 * @since  1.0.7
9069
		 */
9070
		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...
9071
			$this->_logger->entrance();
9072
9073
			if ( $this->is_registered() ) {
9074
				return;
9075
			}
9076
9077
			if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) && fs_request_is_post() ) {
9078
//				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...
9079
9080
				/**
9081
				 * @author Vova Feldman (@svovaf)
9082
				 * @since  1.1.9 Add license key if given.
9083
				 */
9084
				$license_key = fs_request_get( 'license_secret_key' );
9085
9086
				$this->install_with_current_user( $license_key );
9087
			}
9088
		}
9089
9090
9091
		/**
9092
		 * @author Vova Feldman (@svovaf)
9093
		 * @since  1.1.7.4
9094
		 *
9095
		 * @param string|bool $license_key
9096
		 * @param number|bool $trial_plan_id
9097
		 * @param bool        $redirect
9098
		 *
9099
		 * @return string|object If redirect is `false`, returns the next page the user should be redirected to, or the
9100
		 *                       API error object if failed to install.
9101
		 */
9102
		private function install_with_current_user(
9103
			$license_key = false,
9104
			$trial_plan_id = false,
9105
			$redirect = true
9106
		) {
9107
			// Get current logged WP user.
9108
			$current_user = self::_get_current_wp_user();
9109
9110
			// Find the relevant FS user by the email.
9111
			$user = self::_get_user_by_email( $current_user->user_email );
9112
9113
			// We have to set the user before getting user scope API handler.
9114
			$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...
9115
9116
			$extra_install_params = array(
9117
				'uid' => $this->get_anonymous_id(),
9118
			);
9119
9120
			if ( ! empty( $license_key ) ) {
9121
				$filtered_license_key                = $this->apply_filters( 'license_key', $license_key );
9122
				$extra_install_params['license_key'] = $filtered_license_key;
9123
			} else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) {
9124
				$extra_install_params['trial_plan_id'] = $trial_plan_id;
9125
			}
9126
9127
			$args = $this->get_install_data_for_api( $extra_install_params, false, false );
9128
9129
			// Install the plugin.
9130
			$install = $this->get_api_user_scope()->call(
9131
				"/plugins/{$this->get_id()}/installs.json",
9132
				'post',
9133
				$args
9134
			);
9135
9136
			if ( ! $this->is_api_result_entity( $install ) ) {
9137
				if ( ! empty( $args['license_key'] ) ) {
9138
					// Pass full the fully entered license key to the failure handler.
9139
					$args['license_key'] = $license_key;
9140
				}
9141
9142
				$install = $this->apply_filters( 'after_install_failure', $install, $args );
9143
9144
				$this->_admin_notices->add(
9145
					sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
9146
					$this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '<b>' . $install->error->message . '</b>',
9147
					$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
9148
					'error'
9149
				);
9150
9151
				if ( $redirect ) {
9152
                    /**
9153
                     * We set the user before getting the user scope API handler, so the user became temporarily
9154
                     * registered (`is_registered() = true`). Since the API returned an error and we will redirect,
9155
                     * we have to set the user to `null`, otherwise, the user will be redirected to the wrong
9156
                     * activation page based on the return value of `is_registered()`. In addition, in case the
9157
                     * context plugin doesn't have a settings menu and the default page is the `Plugins` page,
9158
                     * misleading plugin activation errors will be shown on the `Plugins` page.
9159
                     *
9160
                     * @author Leo Fajardo (@leorw)
9161
                     */
9162
                    $this->_user = null;
9163
9164
                    fs_redirect( $this->get_activation_url( array( 'error' => $install->error->message ) ) );
9165
				}
9166
9167
				return $install;
9168
			}
9169
9170
			$site        = new FS_Site( $install );
9171
			$this->_site = $site;
9172
9173
			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...
9174
		}
9175
9176
		/**
9177
		 * Tries to activate add-on account based on parent plugin info.
9178
		 *
9179
		 * @author Vova Feldman (@svovaf)
9180
		 * @since  1.0.6
9181
		 *
9182
		 * @param Freemius $parent_fs
9183
		 */
9184
		private function _activate_addon_account( Freemius $parent_fs ) {
9185
			if ( $this->is_registered() ) {
9186
				// Already activated.
9187
				return;
9188
			}
9189
9190
			// Activate add-on with parent plugin credentials.
9191
			$addon_install = $parent_fs->get_api_site_scope()->call(
9192
				"/addons/{$this->_plugin->id}/installs.json",
9193
				'post',
9194
				$this->get_install_data_for_api( array(
9195
					'uid' => $this->get_anonymous_id(),
9196
				), false, false )
9197
			);
9198
9199
			if ( isset( $addon_install->error ) ) {
9200
				$this->_admin_notices->add(
9201
					sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
9202
					$this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '<b>' . $addon_install->error->message . '</b>',
9203
					$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
9204
					'error'
9205
				);
9206
9207
				return;
9208
			}
9209
9210
			// First of all, set site info - otherwise we won't
9211
			// be able to invoke API calls.
9212
			$this->_site = new FS_Site( $addon_install );
9213
9214
			// Sync add-on plans.
9215
			$this->_sync_plans();
9216
9217
			// Get site's current plan.
9218
			$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...
9219
9220
			// Get user information based on parent's plugin.
9221
			$user = $parent_fs->get_user();
9222
9223
			$this->_set_account( $user, $this->_site );
9224
9225
			// Sync licenses.
9226
			$this->_sync_licenses();
9227
9228
			// Try to activate premium license.
9229
			$this->_activate_license( true );
9230
		}
9231
9232
		/**
9233
		 * Tries to activate parent account based on add-on's info.
9234
		 *
9235
		 * @author Vova Feldman (@svovaf)
9236
		 * @since  1.2.2.7
9237
		 *
9238
		 * @param Freemius $parent_fs
9239
		 */
9240
		private function activate_parent_account( Freemius $parent_fs ) {
9241
			if ( ! $this->is_addon() ) {
9242
				// This is not an add-on.
9243
				return;
9244
			}
9245
9246
			if ( $parent_fs->is_registered() ) {
9247
				// Already activated.
9248
				return;
9249
			}
9250
9251
			// Activate parent with add-on's user credentials.
9252
			$parent_install = $this->get_api_user_scope()->call(
9253
				"/plugins/{$parent_fs->_plugin->id}/installs.json",
9254
				'post',
9255
				$parent_fs->get_install_data_for_api( array(
9256
					'uid' => $parent_fs->get_anonymous_id(),
9257
				), false, false )
9258
			);
9259
9260
			if ( isset( $parent_install->error ) ) {
9261
				$this->_admin_notices->add(
9262
					sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
9263
					$this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '<b>' . $parent_install->error->message . '</b>',
9264
					$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
9265
					'error'
9266
				);
9267
9268
				return;
9269
			}
9270
9271
            $parent_fs->_admin_notices->remove_sticky( 'connect_account' );
9272
9273
            if ( $parent_fs->is_pending_activation() ) {
9274
                $parent_fs->_admin_notices->remove_sticky( 'activation_pending' );
9275
9276
                unset( $parent_fs->_storage->is_pending_activation );
9277
            }
9278
9279
			// First of all, set site info - otherwise we won't
9280
			// be able to invoke API calls.
9281
			$parent_fs->_site = new FS_Site( $parent_install );
9282
9283
			// Sync add-on plans.
9284
			$parent_fs->_sync_plans();
9285
9286
			// Get site's current plan.
9287
			$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...
9288
9289
			// Get user information based on parent's plugin.
9290
			$user = $this->get_user();
9291
9292
			$parent_fs->_set_account( $user, $parent_fs->_site );
9293
		}
9294
9295
		#endregion
9296
9297
		#----------------------------------------------------------------------------------
9298
		#region Admin Menu Items
9299
		#----------------------------------------------------------------------------------
9300
9301
		private $_menu_items = array();
9302
9303
		/**
9304
		 * @author Vova Feldman (@svovaf)
9305
		 * @since  1.2.1.8
9306
		 *
9307
		 * @return array
9308
		 */
9309
		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...
9310
			return $this->_menu_items;
9311
		}
9312
9313
		/**
9314
		 * @author Vova Feldman (@svovaf)
9315
		 * @since  1.0.7
9316
		 *
9317
		 * @return string
9318
		 */
9319
		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...
9320
			return $this->_menu->get_slug();
9321
		}
9322
9323
		/**
9324
		 * @author Vova Feldman (@svovaf)
9325
		 * @since  1.0.9
9326
		 */
9327
		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...
9328
//			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...
9329
//				return;
9330
//			}
9331
9332
			if ( ! $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) {
9333
				$this->_menu->remove_menu_item();
9334
			} else {
9335
				$this->do_action( 'before_admin_menu_init' );
9336
9337
				$this->add_menu_action();
9338
				$this->add_submenu_items();
9339
			}
9340
		}
9341
9342
		/**
9343
		 * Admin dashboard menu items modifications.
9344
		 *
9345
		 * NOTE: admin_menu action executed before admin_init.
9346
		 *
9347
		 * @author Vova Feldman (@svovaf)
9348
		 * @since  1.0.7
9349
		 *
9350
		 */
9351
		private function add_menu_action() {
9352
			if ( $this->is_activation_mode() ) {
9353
				if ( $this->is_plugin() || ( $this->has_settings_menu() && ! $this->is_free_wp_org_theme() ) ) {
9354
					$this->override_plugin_menu_with_activation();
9355
				} else {
9356
					/**
9357
					 * Handle theme opt-in when the opt-in form shows as a dialog box in the themes page.
9358
					 */
9359
					if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) {
9360
						add_action( 'load-themes.php', array( &$this, '_install_with_current_user' ) );
9361
					} else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ||
9362
					            fs_request_get_bool( 'pending_activation' )
9363
					) {
9364
						add_action( 'load-themes.php', array( &$this, '_install_with_new_user' ) );
9365
					}
9366
				}
9367
			} else {
9368
				if ( ! $this->is_registered() ) {
9369
					// If not registered try to install user.
9370
					if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) {
9371
						$this->_install_with_new_user();
9372
					}
9373
				} else if (
9374
					fs_request_is_action( 'sync_user' ) &&
9375
					( ! $this->has_settings_menu() || $this->is_free_wp_org_theme() )
9376
				) {
9377
					$this->_handle_account_user_sync();
9378
				}
9379
			}
9380
		}
9381
9382
		/**
9383
		 * @author Vova Feldman (@svovaf)
9384
		 * @since  1.0.1
9385
		 */
9386
		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...
9387
			$this->_logger->entrance();
9388
9389
			$page = strtolower( isset( $_REQUEST['page'] ) ? $_REQUEST['page'] : '' );
9390
9391
			$this->_logger->log( 'page = ' . $page );
9392
9393
			foreach ( $this->_menu_items as $priority => $items ) {
9394
				foreach ( $items as $item ) {
9395
					if ( isset( $item['url'] ) ) {
9396
						if ( $page === $this->_menu->get_slug( strtolower( $item['menu_slug'] ) ) ) {
9397
							$this->_logger->log( 'Redirecting to ' . $item['url'] );
9398
9399
							fs_redirect( $item['url'] );
9400
						}
9401
					}
9402
				}
9403
			}
9404
		}
9405
9406
		/**
9407
		 * Remove plugin's all admin menu items & pages, and replace with activation page.
9408
		 *
9409
		 * @author Vova Feldman (@svovaf)
9410
		 * @since  1.0.1
9411
		 */
9412
		private function override_plugin_menu_with_activation() {
9413
			$this->_logger->entrance();
9414
9415
			$hook = false;
9416
9417
			if ( ! $this->_menu->has_menu() ) {
9418
				// Add the opt-in page without a menu item.
9419
				$hook = FS_Admin_Menu_Manager::add_subpage(
9420
					null,
9421
					$this->get_plugin_name(),
9422
					$this->get_plugin_name(),
9423
					'manage_options',
9424
					$this->_slug,
9425
					array( &$this, '_connect_page_render' )
9426
				);
9427
			} else if ( $this->_menu->is_top_level() ) {
9428
				$hook = $this->_menu->override_menu_item( array( &$this, '_connect_page_render' ) );
9429
9430
				if ( false === $hook ) {
9431
					// Create new menu item just for the opt-in.
9432
					$hook = FS_Admin_Menu_Manager::add_page(
9433
						$this->get_plugin_name(),
9434
						$this->get_plugin_name(),
9435
						'manage_options',
9436
						$this->_menu->get_slug(),
9437
						array( &$this, '_connect_page_render' )
9438
					);
9439
				}
9440
			} else {
9441
				$menus = array( $this->_menu->get_parent_slug() );
9442
9443
				if ( $this->_menu->is_override_exact() ) {
9444
					// Make sure the current page is matching the activation page.
9445
					if ( ! $this->is_matching_url( $this->get_activation_url() ) ) {
9446
						return;
9447
					}
9448
				}
9449
9450
				foreach ( $menus as $parent_slug ) {
9451
					$hook = $this->_menu->override_submenu_action(
9452
						$parent_slug,
9453
						$this->_menu->get_raw_slug(),
9454
						array( &$this, '_connect_page_render' )
9455
					);
9456
9457
					if ( false !== $hook ) {
9458
						// Found plugin's submenu item.
9459
						break;
9460
					}
9461
				}
9462
			}
9463
9464
			if ( $this->is_activation_page() ) {
9465
				// Clean admin page from distracting content.
9466
				self::_clean_admin_content_section();
9467
			}
9468
9469
			if ( false !== $hook ) {
9470
				if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) {
9471
                    $this->_install_with_current_user();
9472
				} else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) {
9473
                    $this->_install_with_new_user();
9474
				}
9475
			}
9476
		}
9477
9478
		/**
9479
		 * @author Leo Fajardo (leorw)
9480
		 * @since  1.2.1
9481
		 *
9482
		 * return string
9483
		 */
9484
		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...
9485
			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...
9486
9487
			$top_level_menu_slug = $this->get_top_level_menu_slug();
9488
9489
			foreach ( $menu as $menu_info ) {
9490
				/**
9491
				 * The second element in the menu info array is the capability/role that has access to the menu and the
9492
				 * third element is the menu slug.
9493
				 */
9494
				if ( $menu_info[2] === $top_level_menu_slug ) {
9495
					return $menu_info[1];
9496
				}
9497
			}
9498
9499
			return 'read';
9500
		}
9501
9502
		/**
9503
		 * @author Vova Feldman (@svovaf)
9504
		 * @since  1.0.0
9505
		 *
9506
		 * @return string
9507
		 */
9508
		private function get_top_level_menu_slug() {
9509
			return ( $this->is_addon() ?
9510
				$this->get_parent_instance()->_menu->get_top_level_menu_slug() :
9511
				$this->_menu->get_top_level_menu_slug() );
9512
		}
9513
9514
		/**
9515
		 * @author Vova Feldman (@svovaf)
9516
		 * @since  1.2.2.7
9517
		 *
9518
		 * @return string
9519
		 */
9520
		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...
9521
			$label = $this->get_text_inline( 'Upgrade', 'upgrade' );
9522
9523
			if ( $this->is_in_trial_promotion() &&
9524
			     ! $this->is_paying_or_trial()
9525
			) {
9526
				// If running a trial promotion, modify the pricing to load the trial.
9527
				$label = $this->get_text_inline( 'Start Trial', 'start-trial' );
9528
			} else if ( $this->is_paying() ) {
9529
				$label = $this->get_text_inline( 'Pricing', 'pricing' );
9530
			}
9531
9532
			return $label;
9533
		}
9534
9535
		/**
9536
		 * @author Vova Feldman (@svovaf)
9537
		 * @since  1.2.2.7
9538
		 *
9539
		 * @return bool
9540
		 */
9541
		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...
9542
			return (
9543
				// Has at least one paid plan.
9544
				$this->has_paid_plan() &&
9545
				// Didn't ask to hide the pricing page.
9546
				$this->is_page_visible( 'pricing' ) &&
9547
				// Don't have a valid active license or has more than one plan.
9548
				( ! $this->is_paying() || ! $this->is_single_plan() )
9549
			);
9550
		}
9551
9552
		/**
9553
		 * Add default Freemius menu items.
9554
		 *
9555
		 * @author Vova Feldman (@svovaf)
9556
		 * @since  1.0.0
9557
		 */
9558
		private function add_submenu_items() {
9559
			$this->_logger->entrance();
9560
9561
			if ( ! $this->is_addon() ) {
9562
				/**
9563
				 * @since 1.2.2.7 Also add submenu items when running in a free .org theme so the tabs will be visible.
9564
				 */
9565
				if ( ! $this->is_activation_mode() || $this->is_free_wp_org_theme() ) {
9566
                    if ( $this->has_affiliate_program() ) {
9567
                        // Add affiliation page.
9568
                        $this->add_submenu_item(
9569
                            $this->get_text_inline( 'Affiliation', 'affiliation' ),
9570
                            array( &$this, '_affiliation_page_render' ),
9571
                            $this->get_plugin_name() . ' &ndash; ' . $this->get_text_inline( 'Affiliation', 'affiliation' ),
9572
                            'manage_options',
9573
                            'affiliation',
9574
                            'Freemius::_clean_admin_content_section',
9575
                            WP_FS__DEFAULT_PRIORITY,
9576
                            $this->is_submenu_item_visible( 'affiliation' )
9577
                        );
9578
                    }
9579
9580
					if ( $this->is_registered() ) {
9581
						$show_account = (
9582
							$this->is_submenu_item_visible( 'account' ) &&
9583
							/**
9584
							 * @since 1.2.2.7 Don't show the Account for free WP.org themes without any paid plans.
9585
							 */
9586
							( ! $this->is_free_wp_org_theme() || $this->has_paid_plan() )
9587
						);
9588
9589
						// Add user account page.
9590
						$this->add_submenu_item(
9591
							$this->get_text_inline( 'Account', 'account' ),
9592
							array( &$this, '_account_page_render' ),
9593
							$this->get_plugin_name() . ' &ndash; ' . $this->get_text_inline( 'Account', 'account' ),
9594
							'manage_options',
9595
							'account',
9596
							array( &$this, '_account_page_load' ),
9597
							WP_FS__DEFAULT_PRIORITY,
9598
							$show_account
9599
						);
9600
					}
9601
9602
					// Add contact page.
9603
					$this->add_submenu_item(
9604
						$this->get_text_inline( 'Contact Us', 'contact-us' ),
9605
						array( &$this, '_contact_page_render' ),
9606
						$this->get_plugin_name() . ' &ndash; ' . $this->get_text_inline( 'Contact Us', 'contact-us' ),
9607
						'manage_options',
9608
						'contact',
9609
						'Freemius::_clean_admin_content_section',
9610
						WP_FS__DEFAULT_PRIORITY,
9611
						$this->is_submenu_item_visible( 'contact' )
9612
					);
9613
9614
					if ( $this->has_addons() ) {
9615
						$this->add_submenu_item(
9616
							$this->get_text_inline( 'Add-Ons', 'add-ons' ),
9617
							array( &$this, '_addons_page_render' ),
9618
							$this->get_plugin_name() . ' &ndash; ' . $this->get_text_inline( 'Add-Ons', 'add-ons' ),
9619
							'manage_options',
9620
							'addons',
9621
							array( &$this, '_addons_page_load' ),
9622
							WP_FS__LOWEST_PRIORITY - 1,
9623
							$this->is_submenu_item_visible( 'addons' )
9624
						);
9625
					}
9626
9627
					$show_pricing = (
9628
						$this->is_submenu_item_visible( 'pricing' ) &&
9629
						$this->is_pricing_page_visible()
9630
					);
9631
9632
					$pricing_cta_text = $this->get_pricing_cta_label();
9633
					$pricing_class    = 'upgrade-mode';
9634
					if ( $show_pricing ) {
9635
						if ( $this->is_in_trial_promotion() &&
9636
						     ! $this->is_paying_or_trial()
9637
						) {
9638
							// If running a trial promotion, modify the pricing to load the trial.
9639
							$pricing_class    = 'trial-mode';
9640
						} else if ( $this->is_paying() ) {
9641
							$pricing_class    = '';
9642
						}
9643
					}
9644
9645
					// Add upgrade/pricing page.
9646
					$this->add_submenu_item(
9647
                        $pricing_cta_text . '&nbsp;&nbsp;' . ( is_rtl() ? '&#x2190;' : '&#x27a4;' ),
9648
						array( &$this, '_pricing_page_render' ),
9649
						$this->get_plugin_name() . ' &ndash; ' . $this->get_text_x_inline( 'Pricing', 'noun', 'pricing' ),
9650
						'manage_options',
9651
						'pricing',
9652
						'Freemius::_clean_admin_content_section',
9653
						WP_FS__LOWEST_PRIORITY,
9654
						$show_pricing,
9655
						$pricing_class
9656
					);
9657
				}
9658
			}
9659
9660
9661
			if ( 0 < count( $this->_menu_items ) ) {
9662
				if ( ! $this->_menu->is_top_level() ) {
9663
					fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
9664
9665
					// Append submenu items right after the plugin's submenu item.
9666
					$this->order_sub_submenu_items();
9667
				} else {
9668
					// Append submenu items.
9669
					$this->embed_submenu_items();
9670
				}
9671
			}
9672
		}
9673
9674
		/**
9675
		 * Moved the actual submenu item additions to a separated function,
9676
		 * in order to support sub-submenu items when the plugin's settings
9677
		 * only have a submenu and not top-level menu item.
9678
		 *
9679
		 * @author Vova Feldman (@svovaf)
9680
		 * @since  1.1.4
9681
		 */
9682
		private function embed_submenu_items() {
9683
			$item_template = $this->_menu->is_top_level() ?
9684
				'<span class="fs-submenu-item %s %s %s">%s</span>' :
9685
				'<span class="fs-submenu-item fs-sub %s %s %s">%s</span>';
9686
9687
			$top_level_menu_capability = $this->get_top_level_menu_capability();
9688
9689
			ksort( $this->_menu_items );
9690
9691
			foreach ( $this->_menu_items as $priority => $items ) {
9692
				foreach ( $items as $item ) {
9693
					$capability = ( ! empty( $item['capability'] ) ? $item['capability'] : $top_level_menu_capability );
9694
9695
					$menu_item = sprintf(
9696
						$item_template,
9697
						$this->get_unique_affix(),
9698
						$item['menu_slug'],
9699
						! empty( $item['class'] ) ? $item['class'] : '',
9700
						$item['menu_title']
9701
					);
9702
9703
					$menu_slug = $this->_menu->get_slug( $item['menu_slug'] );
9704
9705
					if ( ! isset( $item['url'] ) ) {
9706
						$hook = FS_Admin_Menu_Manager::add_subpage(
9707
							$item['show_submenu'] ?
9708
								$this->get_top_level_menu_slug() :
9709
								null,
9710
							$item['page_title'],
9711
							$menu_item,
9712
							$capability,
9713
							$menu_slug,
9714
							$item['render_function']
9715
						);
9716
9717
						if ( false !== $item['before_render_function'] ) {
9718
							add_action( "load-$hook", $item['before_render_function'] );
9719
						}
9720
					} else {
9721
						FS_Admin_Menu_Manager::add_subpage(
9722
							$item['show_submenu'] ?
9723
								$this->get_top_level_menu_slug() :
9724
								null,
9725
							$item['page_title'],
9726
							$menu_item,
9727
							$capability,
9728
							$menu_slug,
9729
							array( $this, '' )
9730
						);
9731
					}
9732
				}
9733
			}
9734
		}
9735
9736
		/**
9737
		 * Re-order the submenu items so all Freemius added new submenu items
9738
		 * are added right after the plugin's settings submenu item.
9739
		 *
9740
		 * @author Vova Feldman (@svovaf)
9741
		 * @since  1.1.4
9742
		 */
9743
		private function order_sub_submenu_items() {
9744
			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...
9745
9746
			$menu_slug = $this->_menu->get_top_level_menu_slug();
9747
9748
			/**
9749
			 * Before "admin_menu" fires, WordPress will loop over the default submenus and remove pages for which the user
9750
			 * does not have permissions. So in case a plugin does not have top-level menu but does have submenus under any
9751
			 * of the default menus, only users that have the right role can access its sub-submenus (Account, Contact Us,
9752
			 * Support Forum, etc.) since $submenu[ $menu_slug ] will be empty if the user doesn't have permission.
9753
			 *
9754
			 * In case a plugin does not have submenus under any of the default menus but does have submenus under the menu
9755
			 * of another plugin, only users that have the right role can access its sub-submenus since we will use the
9756
			 * capability needed to access the parent menu as the capability for the submenus that we will add.
9757
			 */
9758
			if ( empty( $submenu[ $menu_slug ] ) ) {
9759
				return;
9760
			}
9761
9762
			$top_level_menu = &$submenu[ $menu_slug ];
9763
9764
			$all_submenu_items_after = array();
9765
9766
			$found_submenu_item = false;
9767
9768
			foreach ( $top_level_menu as $submenu_id => $meta ) {
9769
				if ( $found_submenu_item ) {
9770
					// Remove all submenu items after the plugin's submenu item.
9771
					$all_submenu_items_after[] = $meta;
9772
					unset( $top_level_menu[ $submenu_id ] );
9773
				}
9774
9775
				if ( $this->_menu->get_raw_slug() === $meta[2] ) {
9776
					// Found the submenu item, put all below.
9777
					$found_submenu_item = true;
9778
					continue;
9779
				}
9780
			}
9781
9782
			// Embed all plugin's new submenu items.
9783
			$this->embed_submenu_items();
9784
9785
			// Start with specially high number to make sure it's appended.
9786
			$i = max( 10000, max( array_keys( $top_level_menu ) ) + 1 );
9787
			foreach ( $all_submenu_items_after as $meta ) {
9788
				$top_level_menu[ $i ] = $meta;
9789
				$i ++;
9790
			}
9791
9792
			// Sort submenu items.
9793
			ksort( $top_level_menu );
9794
		}
9795
9796
		/**
9797
		 * Helper method to return the module's support forum URL.
9798
		 *
9799
		 * @author Vova Feldman (@svovaf)
9800
		 * @since  1.2.2.7
9801
		 *
9802
		 * @return string
9803
		 */
9804
		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...
9805
			return $this->apply_filters( 'support_forum_url', "https://wordpress.org/support/{$this->_module_type}/{$this->_slug}" );
9806
		}
9807
9808
		/**
9809
		 * Displays the Support Forum link when enabled.
9810
		 *
9811
		 * Can be filtered like so:
9812
		 *
9813
		 *  function _fs_show_support_menu( $is_visible, $menu_id ) {
9814
		 *      if ( 'support' === $menu_id ) {
9815
		 *            return _fs->is_registered();
9816
		 *        }
9817
		 *        return $is_visible;
9818
		 *    }
9819
		 *    _fs()->add_filter('is_submenu_visible', '_fs_show_support_menu', 10, 2);
9820
		 *
9821
		 */
9822
		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...
9823
			if ( ! $this->is_on() ) {
9824
				return;
9825
			}
9826
9827
			if ( ! $this->is_activation_mode() ) {
9828
				$this->add_submenu_link_item(
9829
					$this->apply_filters( 'support_forum_submenu', $this->get_text_inline( 'Support Forum', 'support-forum' ) ),
9830
					$this->get_support_forum_url(),
9831
					'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...
9832
					null,
9833
					50,
9834
					$this->is_submenu_item_visible( 'support' )
9835
				);
9836
			}
9837
		}
9838
9839
		/**
9840
		 * @author Vova Feldman (@svovaf)
9841
		 * @since  1.0.1
9842
		 *
9843
		 * @param string        $menu_title
9844
		 * @param callable      $render_function
9845
		 * @param bool|string   $page_title
9846
		 * @param string        $capability
9847
		 * @param bool|string   $menu_slug
9848
		 * @param bool|callable $before_render_function
9849
		 * @param int           $priority
9850
		 * @param bool          $show_submenu
9851
		 * @param string        $class Since 1.2.1.5 can add custom classes to menu items.
9852
		 */
9853
		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...
9854
			$menu_title,
9855
			$render_function,
9856
			$page_title = false,
9857
			$capability = 'manage_options',
9858
			$menu_slug = false,
9859
			$before_render_function = false,
9860
			$priority = WP_FS__DEFAULT_PRIORITY,
9861
			$show_submenu = true,
9862
			$class = ''
9863
		) {
9864
			$this->_logger->entrance( 'Title = ' . $menu_title );
9865
9866
			if ( $this->is_addon() ) {
9867
				$parent_fs = $this->get_parent_instance();
9868
9869
				if ( is_object( $parent_fs ) ) {
9870
					$parent_fs->add_submenu_item(
9871
						$menu_title,
9872
						$render_function,
9873
						$page_title,
9874
						$capability,
9875
						$menu_slug,
9876
						$before_render_function,
9877
						$priority,
9878
						$show_submenu,
9879
						$class
9880
					);
9881
9882
					return;
9883
				}
9884
			}
9885
9886
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
9887
				$this->_menu_items[ $priority ] = array();
9888
			}
9889
9890
			$this->_menu_items[ $priority ][] = array(
9891
				'page_title'             => is_string( $page_title ) ? $page_title : $menu_title,
9892
				'menu_title'             => $menu_title,
9893
				'capability'             => $capability,
9894
				'menu_slug'              => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ),
9895
				'render_function'        => $render_function,
9896
				'before_render_function' => $before_render_function,
9897
				'show_submenu'           => $show_submenu,
9898
				'class'                  => $class,
9899
			);
9900
		}
9901
9902
		/**
9903
		 * @author Vova Feldman (@svovaf)
9904
		 * @since  1.0.1
9905
		 *
9906
		 * @param string $menu_title
9907
		 * @param string $url
9908
		 * @param bool   $menu_slug
9909
		 * @param string $capability
9910
		 * @param int    $priority
9911
		 * @param bool   $show_submenu
9912
		 */
9913
		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...
9914
			$menu_title,
9915
			$url,
9916
			$menu_slug = false,
9917
			$capability = 'read',
9918
			$priority = WP_FS__DEFAULT_PRIORITY,
9919
			$show_submenu = true
9920
		) {
9921
			$this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url );
9922
9923
			if ( $this->is_addon() ) {
9924
				$parent_fs = $this->get_parent_instance();
9925
9926
				if ( is_object( $parent_fs ) ) {
9927
					$parent_fs->add_submenu_link_item(
9928
						$menu_title,
9929
						$url,
9930
						$menu_slug,
9931
						$capability,
9932
						$priority,
9933
						$show_submenu
9934
					);
9935
9936
					return;
9937
				}
9938
			}
9939
9940
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
9941
				$this->_menu_items[ $priority ] = array();
9942
			}
9943
9944
			$this->_menu_items[ $priority ][] = array(
9945
				'menu_title'             => $menu_title,
9946
				'capability'             => $capability,
9947
				'menu_slug'              => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ),
9948
				'url'                    => $url,
9949
				'page_title'             => $menu_title,
9950
				'render_function'        => 'fs_dummy',
9951
				'before_render_function' => '',
9952
				'show_submenu'           => $show_submenu,
9953
			);
9954
		}
9955
9956
		#endregion ------------------------------------------------------------------
9957
9958
9959
		#--------------------------------------------------------------------------------
9960
		#region Actions / Hooks / Filters
9961
		#--------------------------------------------------------------------------------
9962
9963
		/**
9964
		 * @author Vova Feldman (@svovaf)
9965
		 * @since  1.1.7
9966
		 *
9967
		 * @param string $tag
9968
		 *
9969
		 * @return string
9970
		 */
9971
		public function get_action_tag( $tag ) {
9972
			return self::get_action_tag_static( $tag, $this->_slug, $this->is_plugin() );
9973
		}
9974
9975
		/**
9976
		 * @author Vova Feldman (@svovaf)
9977
		 * @since  1.2.1.6
9978
		 *
9979
		 * @param string $tag
9980
		 * @param string $slug
9981
		 * @param bool   $is_plugin
9982
		 *
9983
		 * @return string
9984
		 */
9985
		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...
9986
			$action = "fs_{$tag}";
9987
9988
			if ( ! empty( $slug ) ) {
9989
				$action .= '_' . self::get_module_unique_affix( $slug, $is_plugin );
9990
			}
9991
9992
			return $action;
9993
		}
9994
9995
		/**
9996
		 * Returns a string that can be used to generate a unique action name,
9997
		 * option name, HTML element ID, or HTML element class.
9998
		 *
9999
		 * @author Leo Fajardo (@leorw)
10000
		 * @since  1.2.2
10001
		 *
10002
		 * @return string
10003
		 */
10004
		public function get_unique_affix() {
10005
			return self::get_module_unique_affix( $this->_slug, $this->is_plugin() );
10006
		}
10007
10008
		/**
10009
		 * Returns a string that can be used to generate a unique action name,
10010
		 * option name, HTML element ID, or HTML element class.
10011
		 *
10012
		 * @author Vova Feldman (@svovaf)
10013
		 * @since  1.2.2.5
10014
		 *
10015
		 * @param string $slug
10016
		 * @param bool   $is_plugin
10017
		 *
10018
		 * @return string
10019
		 */
10020
		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...
10021
			$affix = $slug;
10022
10023
			if ( ! $is_plugin ) {
10024
				$affix .= '-' . WP_FS__MODULE_TYPE_THEME;
10025
			}
10026
10027
			return $affix;
10028
		}
10029
10030
		/**
10031
		 * @author Vova Feldman (@svovaf)
10032
		 * @since  1.2.1
10033
		 * @since  1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are
10034
		 *         based on the slug for backward compatibility.
10035
		 *
10036
		 * @param string $tag
10037
		 *
10038
		 * @return string
10039
		 */
10040
		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...
10041
			return self::get_ajax_action_static( $tag, $this->_module_id );
10042
		}
10043
10044
		/**
10045
		 * @author Vova Feldman (@svovaf)
10046
		 * @since  1.2.1.7
10047
		 *
10048
		 * @param string $tag
10049
		 *
10050
		 * @return string
10051
		 */
10052
		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...
10053
			return wp_create_nonce( $this->get_ajax_action( $tag ) );
10054
		}
10055
10056
		/**
10057
		 * @author Vova Feldman (@svovaf)
10058
		 * @since  1.2.1.7
10059
		 *
10060
		 * @param string $tag
10061
		 */
10062
		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...
10063
			check_ajax_referer( $this->get_ajax_action( $tag ), 'security' );
10064
		}
10065
10066
		/**
10067
		 * @author Vova Feldman (@svovaf)
10068
		 * @since  1.2.1.6
10069
		 * @since  1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are
10070
		 *         based on the slug for backward compatibility.
10071
		 *
10072
		 * @param string      $tag
10073
		 * @param number|null $module_id
10074
		 *
10075
		 * @return string
10076
		 */
10077
		private static function get_ajax_action_static( $tag, $module_id = null ) {
10078
			$action = "fs_{$tag}";
10079
10080
			if ( ! empty( $module_id ) ) {
10081
				$action .= "_{$module_id}";
10082
			}
10083
10084
			return $action;
10085
		}
10086
10087
		/**
10088
		 * Do action, specific for the current context plugin.
10089
		 *
10090
		 * @author Vova Feldman (@svovaf)
10091
		 * @since  1.0.1
10092
		 *
10093
		 * @param string $tag     The name of the action to be executed.
10094
		 * @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...
10095
		 *                        functions hooked to the action. Default empty.
10096
		 *
10097
		 * @uses   do_action()
10098
		 */
10099
		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...
10100
			$this->_logger->entrance( $tag );
10101
10102
			$args = func_get_args();
10103
10104
			call_user_func_array( 'do_action', array_merge(
10105
					array( $this->get_action_tag( $tag ) ),
10106
					array_slice( $args, 1 ) )
10107
			);
10108
		}
10109
10110
		/**
10111
		 * Add action, specific for the current context plugin.
10112
		 *
10113
		 * @author Vova Feldman (@svovaf)
10114
		 * @since  1.0.1
10115
		 *
10116
		 * @param string   $tag
10117
		 * @param callable $function_to_add
10118
		 * @param int      $priority
10119
		 * @param int      $accepted_args
10120
		 *
10121
		 * @uses   add_action()
10122
		 */
10123
		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...
10124
			$tag,
10125
			$function_to_add,
10126
			$priority = WP_FS__DEFAULT_PRIORITY,
10127
			$accepted_args = 1
10128
		) {
10129
			$this->_logger->entrance( $tag );
10130
10131
			add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
10132
		}
10133
10134
		/**
10135
		 * Add AJAX action, specific for the current context plugin.
10136
		 *
10137
		 * @author Vova Feldman (@svovaf)
10138
		 * @since  1.2.1
10139
		 *
10140
		 * @param string   $tag
10141
		 * @param callable $function_to_add
10142
		 * @param int      $priority
10143
		 *
10144
		 * @uses   add_action()
10145
		 *
10146
		 * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
10147
		 */
10148
		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...
10149
			$tag,
10150
			$function_to_add,
10151
			$priority = WP_FS__DEFAULT_PRIORITY
10152
		) {
10153
			$this->_logger->entrance( $tag );
10154
10155
			return self::add_ajax_action_static(
10156
				$tag,
10157
				$function_to_add,
10158
				$priority,
10159
				$this->_module_id
10160
			);
10161
		}
10162
10163
		/**
10164
		 * Add AJAX action.
10165
		 *
10166
		 * @author Vova Feldman (@svovaf)
10167
		 * @since  1.2.1.6
10168
		 *
10169
		 * @param string      $tag
10170
		 * @param callable    $function_to_add
10171
		 * @param int         $priority
10172
		 * @param number|null $module_id
10173
		 *
10174
		 * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
10175
		 * @uses   add_action()
10176
		 *
10177
		 */
10178
		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...
10179
			$tag,
10180
			$function_to_add,
10181
			$priority = WP_FS__DEFAULT_PRIORITY,
10182
			$module_id = null
10183
		) {
10184
			self::$_static_logger->entrance( $tag );
10185
10186
			if ( ! self::is_ajax_action_static( $tag, $module_id ) ) {
10187
				return false;
10188
			}
10189
10190
			add_action(
10191
				'wp_ajax_' . self::get_ajax_action_static( $tag, $module_id ),
10192
				$function_to_add,
10193
				$priority,
10194
				0
10195
			);
10196
10197
			self::$_static_logger->info( "$tag AJAX callback action added." );
10198
10199
			return true;
10200
		}
10201
10202
		/**
10203
		 * Send a JSON response back to an Ajax request.
10204
		 *
10205
		 * @author Vova Feldman (@svovaf)
10206
		 * @since  1.2.1.5
10207
		 *
10208
		 * @param mixed $response
10209
		 */
10210
		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...
10211
			wp_send_json( $response );
10212
		}
10213
10214
		/**
10215
		 * Send a JSON response back to an Ajax request, indicating success.
10216
		 *
10217
		 * @author Vova Feldman (@svovaf)
10218
		 * @since  1.2.1.5
10219
		 *
10220
		 * @param mixed $data Data to encode as JSON, then print and exit.
10221
		 */
10222
		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...
10223
			wp_send_json_success( $data );
10224
		}
10225
10226
		/**
10227
		 * Send a JSON response back to an Ajax request, indicating failure.
10228
		 *
10229
		 * @author Vova Feldman (@svovaf)
10230
		 * @since  1.2.1.5
10231
		 *
10232
		 * @param mixed $error Optional error message.
10233
		 */
10234
		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...
10235
			$result = array( 'success' => false );
10236
			if ( ! empty( $error ) ) {
10237
				$result['error'] = $error;
10238
			}
10239
10240
			wp_send_json( $result );
10241
		}
10242
10243
		/**
10244
		 * Apply filter, specific for the current context plugin.
10245
		 *
10246
		 * @author Vova Feldman (@svovaf)
10247
		 * @since  1.0.9
10248
		 *
10249
		 * @param string $tag   The name of the filter hook.
10250
		 * @param mixed  $value The value on which the filters hooked to `$tag` are applied on.
10251
		 *
10252
		 * @return mixed The filtered value after all hooked functions are applied to it.
10253
		 *
10254
		 * @uses   apply_filters()
10255
		 */
10256
		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...
10257
			$this->_logger->entrance( $tag );
10258
10259
			$args = func_get_args();
10260
			array_unshift( $args, $this->get_unique_affix() );
10261
10262
			return call_user_func_array( 'fs_apply_filter', $args );
10263
		}
10264
10265
		/**
10266
		 * Add filter, specific for the current context plugin.
10267
		 *
10268
		 * @author Vova Feldman (@svovaf)
10269
		 * @since  1.0.9
10270
		 *
10271
		 * @param string   $tag
10272
		 * @param callable $function_to_add
10273
		 * @param int      $priority
10274
		 * @param int      $accepted_args
10275
		 *
10276
		 * @uses   add_filter()
10277
		 */
10278
		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...
10279
			$this->_logger->entrance( $tag );
10280
10281
			add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
10282
		}
10283
10284
		/**
10285
		 * Check if has filter.
10286
		 *
10287
		 * @author Vova Feldman (@svovaf)
10288
		 * @since  1.1.4
10289
		 *
10290
		 * @param string        $tag
10291
		 * @param callable|bool $function_to_check Optional. The callback to check for. Default false.
10292
		 *
10293
		 * @return false|int
10294
		 *
10295
		 * @uses   has_filter()
10296
		 */
10297
		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...
10298
			$this->_logger->entrance( $tag );
10299
10300
			return has_filter( $this->get_action_tag( $tag ), $function_to_check );
10301
		}
10302
10303
		#endregion
10304
10305
		/**
10306
		 * Override default i18n text phrases.
10307
		 *
10308
		 * @author Vova Feldman (@svovaf)
10309
		 * @since  1.1.6
10310
		 *
10311
		 * @param string[] string $key_value
10312
		 *
10313
		 * @uses   fs_override_i18n()
10314
		 */
10315
		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...
10316
			fs_override_i18n( $key_value, $this->_slug );
10317
		}
10318
10319
		/* Account Page
10320
		------------------------------------------------------------------------------------------------------------------*/
10321
		/**
10322
		 * Update site information.
10323
		 *
10324
		 * @author Vova Feldman (@svovaf)
10325
		 * @since  1.0.1
10326
		 *
10327
		 * @param bool $store Flush to Database if true.
10328
		 */
10329
		private function _store_site( $store = true ) {
10330
			$this->_logger->entrance();
10331
10332
			if ( empty( $this->_site->id ) ) {
10333
				$this->_logger->error( "Empty install ID, can't store site." );
10334
10335
				return;
10336
			}
10337
10338
			$encrypted_site       = clone $this->_site;
10339
			$encrypted_site->plan = self::_encrypt_entity( $this->_site->plan );
10340
10341
			$sites = self::get_all_sites( $this->_module_type );
10342
10343
            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...
10344
                /**
10345
                 * Store the current user ID as the previous user ID so that the previous user can be used
10346
                 * as the install's owner while the new owner's details are not yet available.
10347
                 *
10348
                 * This will be executed only in the `replica` site. For example, there are 2 sites, namely `original`
10349
                 * and `replica`, then an ownership change was initiated and completed in the `original`, the `replica`
10350
                 * will be using the previous user until it is updated again (e.g.: until the next clone of `original`
10351
                 * into `replica`.
10352
                 *
10353
                 * @author Leo Fajardo (@leorw)
10354
                 */
10355
                $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...
10356
            }
10357
10358
			$sites[ $this->_slug ] = $encrypted_site;
10359
10360
			$this->set_account_option( 'sites', $sites, $store );
10361
		}
10362
10363
		/**
10364
		 * Update plugin's plans information.
10365
		 *
10366
		 * @author Vova Feldman (@svovaf)
10367
		 * @since  1.0.2
10368
		 *
10369
		 * @param bool $store Flush to Database if true.
10370
		 */
10371
		private function _store_plans( $store = true ) {
10372
			$this->_logger->entrance();
10373
10374
			$plans = self::get_all_plans( $this->_module_type );
10375
10376
			// Copy plans.
10377
			$encrypted_plans = array();
10378
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
10379
				$encrypted_plans[] = self::_encrypt_entity( $this->_plans[ $i ] );
10380
			}
10381
10382
			$plans[ $this->_slug ] = $encrypted_plans;
10383
10384
			$this->set_account_option( 'plans', $plans, $store );
10385
		}
10386
10387
		/**
10388
		 * Update user's plugin licenses.
10389
		 *
10390
		 * @author Vova Feldman (@svovaf)
10391
		 * @since  1.0.5
10392
		 *
10393
		 * @param bool                $store
10394
		 * @param string|bool         $plugin_slug
10395
		 * @param FS_Plugin_License[] $licenses
10396
		 */
10397
		private function _store_licenses( $store = true, $plugin_slug = false, $licenses = array() ) {
10398
			$this->_logger->entrance();
10399
10400
			$all_licenses = self::get_all_licenses( $this->_module_type );
10401
10402
			if ( ! is_string( $plugin_slug ) ) {
10403
				$plugin_slug = $this->_slug;
10404
				$licenses    = $this->_licenses;
10405
			}
10406
10407
			if ( ! isset( $all_licenses[ $plugin_slug ] ) ) {
10408
				$all_licenses[ $plugin_slug ] = array();
10409
			}
10410
10411
			$all_licenses[ $plugin_slug ][ $this->_user->id ] = $licenses;
10412
10413
			$this->set_account_option( 'licenses', $all_licenses, $store );
10414
		}
10415
10416
		/**
10417
		 * Update user information.
10418
		 *
10419
		 * @author Vova Feldman (@svovaf)
10420
		 * @since  1.0.1
10421
		 *
10422
		 * @param bool $store Flush to Database if true.
10423
		 */
10424
		private function _store_user( $store = true ) {
10425
			$this->_logger->entrance();
10426
10427
			if ( empty( $this->_user->id ) ) {
10428
				$this->_logger->error( "Empty user ID, can't store user." );
10429
10430
				return;
10431
			}
10432
10433
			$users                     = self::get_all_users();
10434
			$users[ $this->_user->id ] = $this->_user;
10435
			self::$_accounts->set_option( 'users', $users, $store );
10436
		}
10437
10438
		/**
10439
		 * Update new updates information.
10440
		 *
10441
		 * @author Vova Feldman (@svovaf)
10442
		 * @since  1.0.4
10443
		 *
10444
		 * @param FS_Plugin_Tag|null $update
10445
		 * @param bool               $store Flush to Database if true.
10446
		 * @param bool|number        $plugin_id
10447
		 */
10448
		private function _store_update( $update, $store = true, $plugin_id = false ) {
10449
			$this->_logger->entrance();
10450
10451
			if ( $update instanceof FS_Plugin_Tag ) {
10452
				$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...
10453
			}
10454
10455
			if ( ! is_numeric( $plugin_id ) ) {
10456
				$plugin_id = $this->_plugin->id;
10457
			}
10458
10459
			$updates               = self::get_all_updates();
10460
			$updates[ $plugin_id ] = $update;
10461
			self::$_accounts->set_option( 'updates', $updates, $store );
10462
		}
10463
10464
		/**
10465
		 * Update new updates information.
10466
		 *
10467
		 * @author   Vova Feldman (@svovaf)
10468
		 * @since    1.0.6
10469
		 *
10470
		 * @param FS_Plugin[] $plugin_addons
10471
		 * @param bool        $store Flush to Database if true.
10472
		 */
10473
		private function _store_addons( $plugin_addons, $store = true ) {
10474
			$this->_logger->entrance();
10475
10476
			$addons                       = self::get_all_addons();
10477
			$addons[ $this->_plugin->id ] = $plugin_addons;
10478
			self::$_accounts->set_option( 'addons', $addons, $store );
10479
		}
10480
10481
		/**
10482
		 * Delete plugin's associated add-ons.
10483
		 *
10484
		 * @author   Vova Feldman (@svovaf)
10485
		 * @since    1.0.8
10486
		 *
10487
		 * @param bool $store
10488
		 *
10489
		 * @return bool
10490
		 */
10491
		private function _delete_account_addons( $store = true ) {
10492
			$all_addons = self::get_all_account_addons();
10493
10494
			if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) {
10495
				return false;
10496
			}
10497
10498
			unset( $all_addons[ $this->_plugin->id ] );
10499
10500
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
10501
10502
			return true;
10503
		}
10504
10505
		/**
10506
		 * Update account add-ons list.
10507
		 *
10508
		 * @author   Vova Feldman (@svovaf)
10509
		 * @since    1.0.6
10510
		 *
10511
		 * @param FS_Plugin[] $addons
10512
		 * @param bool        $store Flush to Database if true.
10513
		 */
10514
		private function _store_account_addons( $addons, $store = true ) {
10515
			$this->_logger->entrance();
10516
10517
			$all_addons                       = self::get_all_account_addons();
10518
			$all_addons[ $this->_plugin->id ] = $addons;
10519
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
10520
		}
10521
10522
		/**
10523
		 * Store account params in the Database.
10524
		 *
10525
		 * @author Vova Feldman (@svovaf)
10526
		 * @since  1.0.1
10527
		 */
10528
		private function _store_account() {
10529
			$this->_logger->entrance();
10530
10531
			$this->_store_site( false );
10532
			$this->_store_user( false );
10533
			$this->_store_plans( false );
10534
			$this->_store_licenses( false );
10535
10536
			self::$_accounts->store();
10537
		}
10538
10539
		/**
10540
		 * Sync user's information.
10541
		 *
10542
		 * @author Vova Feldman (@svovaf)
10543
		 * @since  1.0.3
10544
		 * @uses   FS_Api
10545
		 */
10546
		private function _handle_account_user_sync() {
10547
			$this->_logger->entrance();
10548
10549
			$api = $this->get_api_user_scope();
10550
10551
			// Get user's information.
10552
			$user = $api->get( '/', true );
10553
10554
			if ( isset( $user->id ) ) {
10555
				$this->_user->first = $user->first;
10556
				$this->_user->last  = $user->last;
10557
				$this->_user->email = $user->email;
10558
10559
				$is_menu_item_account_visible = $this->is_submenu_item_visible( 'account' );
10560
10561
				if ( $user->is_verified &&
10562
				     ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified )
10563
				) {
10564
					$this->_user->is_verified = true;
10565
10566
					$this->do_action( 'account_email_verified', $user->email );
10567
10568
					$this->_admin_notices->add(
10569
						$this->get_text_inline( 'Your email has been successfully verified - you are AWESOME!', 'email-verified-message' ),
10570
						$this->get_text_x_inline( 'Right on', 'a positive response', 'right-on' ) . '!',
10571
						'success',
10572
						// Make admin sticky if account menu item is invisible,
10573
						// since the page will be auto redirected to the plugin's
10574
						// main settings page, and the non-sticky message
10575
						// will disappear.
10576
						! $is_menu_item_account_visible,
10577
						false,
10578
						'email_verified'
10579
					);
10580
				}
10581
10582
				// Flush user details to DB.
10583
				$this->_store_user();
10584
10585
				$this->do_action( 'after_account_user_sync', $user );
10586
10587
				/**
10588
				 * If account menu item is hidden, redirect to plugin's main settings page.
10589
				 *
10590
				 * @author Vova Feldman (@svovaf)
10591
				 * @since  1.1.6
10592
				 *
10593
				 * @link   https://github.com/Freemius/wordpress-sdk/issues/6
10594
				 */
10595
				if ( ! $is_menu_item_account_visible ) {
10596
					fs_redirect( $this->_get_admin_page_url() );
10597
				}
10598
			}
10599
		}
10600
10601
		/**
10602
		 * @param bool $store
10603
		 *
10604
		 * @return FS_Plugin_Plan|object|false
10605
		 */
10606
		private function _enrich_site_plan( $store = true ) {
10607
			// Try to load plan from local cache.
10608
			$plan = $this->_get_plan_by_id( $this->_site->plan->id );
10609
10610
			if ( false === $plan ) {
10611
				$plan = $this->_fetch_site_plan();
10612
			}
10613
10614
			if ( $plan instanceof FS_Plugin_Plan ) {
10615
				$this->_update_plan( $plan, $store );
10616
			}
10617
10618
			return $plan;
10619
		}
10620
10621
		/**
10622
		 * @author Vova Feldman (@svovaf)
10623
		 * @since  1.0.9
10624
		 * @uses   FS_Api
10625
		 *
10626
		 * @param bool $store
10627
		 *
10628
		 * @return FS_Plugin_Plan|object|false
10629
		 */
10630
		private function _enrich_site_trial_plan( $store = true ) {
10631
			// Try to load plan from local cache.
10632
			$trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id );
10633
10634
			if ( false === $trial_plan ) {
10635
				$trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id );
10636
			}
10637
10638
			if ( $trial_plan instanceof FS_Plugin_Plan ) {
10639
				$this->_storage->store( 'trial_plan', $trial_plan, $store );
10640
			}
10641
10642
			return $trial_plan;
10643
		}
10644
10645
		/**
10646
		 * @author Vova Feldman (@svovaf)
10647
		 * @since  1.0.9
10648
		 * @uses   FS_Api
10649
		 *
10650
		 * @param number|bool $license_id
10651
		 *
10652
		 * @return FS_Subscription|object|bool
10653
		 */
10654
		private function _fetch_site_license_subscription( $license_id = false ) {
10655
			$this->_logger->entrance();
10656
			$api = $this->get_api_site_scope();
10657
10658
			if ( ! is_numeric( $license_id ) ) {
10659
				$license_id = $this->_license->id;
10660
			}
10661
10662
			$result = $api->get( "/licenses/{$license_id}/subscriptions.json", true );
10663
10664
			return ! isset( $result->error ) ?
10665
				( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ?
10666
					new FS_Subscription( $result->subscriptions[0] ) :
10667
					false
10668
				) :
10669
				$result;
10670
		}
10671
10672
		/**
10673
		 * @author Vova Feldman (@svovaf)
10674
		 * @since  1.0.4
10675
		 * @uses   FS_Api
10676
		 *
10677
		 * @param number|bool $plan_id
10678
		 *
10679
		 * @return FS_Plugin_Plan|object
10680
		 */
10681
		private function _fetch_site_plan( $plan_id = false ) {
10682
			$this->_logger->entrance();
10683
			$api = $this->get_api_site_scope();
10684
10685
			if ( ! is_numeric( $plan_id ) ) {
10686
				$plan_id = $this->_site->plan->id;
10687
			}
10688
10689
			$plan = $api->get( "/plans/{$plan_id}.json", true );
10690
10691
			return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan;
10692
		}
10693
10694
		/**
10695
		 * @author Vova Feldman (@svovaf)
10696
		 * @since  1.0.5
10697
		 * @uses   FS_Api
10698
		 *
10699
		 * @return FS_Plugin_Plan[]|object
10700
		 */
10701
		private function _fetch_plugin_plans() {
10702
            $this->_logger->entrance();
10703
            $api = $this->get_api_site_scope();
10704
10705
            /**
10706
             * @since 1.2.3 When running in DEV mode, retrieve pending plans as well.
10707
             */
10708
            $result = $api->get( '/plans.json?show_pending=' . ( $this->has_secret_key() ? 'true' : 'false' ), true );
10709
10710
            if ( $this->is_api_result_object( $result, 'plans' ) && is_array( $result->plans ) ) {
10711
                for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) {
10712
                    $result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] );
10713
                }
10714
10715
                $result = $result->plans;
10716
            }
10717
10718
            return $result;
10719
        }
10720
10721
		/**
10722
		 * @author Vova Feldman (@svovaf)
10723
		 * @since  1.0.5
10724
		 * @uses   FS_Api
10725
		 *
10726
		 * @param number|bool $plugin_id
10727
		 * @param number|bool $site_license_id
10728
		 *
10729
		 * @return FS_Plugin_License[]|object
10730
		 */
10731
		private function _fetch_licenses( $plugin_id = false, $site_license_id = false ) {
10732
			$this->_logger->entrance();
10733
10734
			$api = $this->get_api_user_scope();
10735
10736
			if ( ! is_numeric( $plugin_id ) ) {
10737
				$plugin_id = $this->_plugin->id;
10738
			}
10739
10740
			$result = $api->get( "/plugins/{$plugin_id}/licenses.json", true );
10741
10742
			$is_site_license_synced = false;
10743
10744
			$api_errors = array();
10745
10746
			if ( $this->is_api_result_object( $result, 'licenses' ) &&
10747
			     is_array( $result->licenses )
10748
			) {
10749
				for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) {
10750
					$result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] );
10751
10752
					if ( ( ! $is_site_license_synced ) && is_numeric( $site_license_id ) ) {
10753
						$is_site_license_synced = ( $site_license_id == $result->licenses[ $i ]->id );
10754
					}
10755
				}
10756
10757
				$result = $result->licenses;
10758
			} else {
10759
				$api_errors[] = $result;
10760
				$result       = array();
10761
			}
10762
10763
			if ( ! $is_site_license_synced ) {
10764
				$api = $this->get_api_site_scope();
10765
10766
				if ( is_numeric( $site_license_id ) ) {
10767
					// Try to retrieve a foreign license that is linked to the install.
10768
					$api_result = $api->call( '/licenses.json' );
10769
10770
					if ( $this->is_api_result_object( $api_result, 'licenses' ) &&
10771
					     is_array( $api_result->licenses )
10772
					) {
10773
						$licenses = $api_result->licenses;
10774
10775
						if ( ! empty( $licenses ) ) {
10776
							$result[] = new FS_Plugin_License( $licenses[0] );
10777
						}
10778
					} else {
10779
						$api_errors[] = $api_result;
10780
					}
10781
				} else if ( is_object( $this->_license ) ) {
10782
					// Fetch foreign license by ID and license key.
10783
					$license = $api->get( "/licenses/{$this->_license->id}.json?license_key=" .
10784
					                      urlencode( $this->_license->secret_key ) );
10785
10786
					if ( $this->is_api_result_entity( $license ) ) {
10787
						$result[] = new FS_Plugin_License( $license );
10788
					} else {
10789
						$api_errors[] = $license;
10790
					}
10791
				}
10792
			}
10793
10794
			if ( is_array( $result ) && 0 < count( $result ) ) {
10795
				// If found at least one license, return license collection even if there are errors.
10796
				return $result;
10797
			}
10798
10799
			if ( ! empty( $api_errors ) ) {
10800
				// If found any errors and no licenses, return first error.
10801
				return $api_errors[0];
10802
			}
10803
10804
			// Fallback to empty licenses list.
10805
			return $result;
10806
		}
10807
10808
		/**
10809
		 * @author Vova Feldman (@svovaf)
10810
		 * @since  1.2.0
10811
		 * @uses   FS_Api
10812
		 *
10813
		 * @param number|bool $plugin_id
10814
		 * @param bool        $flush
10815
		 *
10816
		 * @return FS_Payment[]|object
10817
		 */
10818
		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...
10819
			$this->_logger->entrance();
10820
10821
			$api = $this->get_api_user_scope();
10822
10823
			if ( ! is_numeric( $plugin_id ) ) {
10824
				$plugin_id = $this->_plugin->id;
10825
			}
10826
10827
			$result = $api->get( "/plugins/{$plugin_id}/payments.json?include_addons=true", $flush );
10828
10829
			if ( ! isset( $result->error ) ) {
10830
				for ( $i = 0, $len = count( $result->payments ); $i < $len; $i ++ ) {
10831
					$result->payments[ $i ] = new FS_Payment( $result->payments[ $i ] );
10832
				}
10833
				$result = $result->payments;
10834
			}
10835
10836
			return $result;
10837
		}
10838
10839
		/**
10840
		 * @author Vova Feldman (@svovaf)
10841
		 * @since  1.2.1.5
10842
		 * @uses   FS_Api
10843
		 *
10844
		 * @param bool $flush
10845
		 *
10846
		 * @return \FS_Billing|mixed
10847
		 */
10848
		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...
10849
			require_once WP_FS__DIR_INCLUDES . '/entities/class-fs-billing.php';
10850
10851
			$billing = $this->get_api_user_scope()->get( 'billing.json', $flush );
10852
10853
			if ( $this->is_api_result_entity( $billing ) ) {
10854
				$billing = new FS_Billing( $billing );
10855
			}
10856
10857
			return $billing;
10858
		}
10859
10860
		/**
10861
		 * @author Vova Feldman (@svovaf)
10862
		 * @since  1.0.4
10863
		 *
10864
		 * @param FS_Plugin_Plan $plan
10865
		 * @param bool           $store
10866
		 */
10867
		private function _update_plan( $plan, $store = false ) {
10868
			$this->_logger->entrance();
10869
10870
			$this->_site->plan = $plan;
10871
			$this->_store_site( $store );
10872
		}
10873
10874
		/**
10875
		 * @author Vova Feldman (@svovaf)
10876
		 * @since  1.0.5
10877
		 *
10878
		 * @param FS_Plugin_License[] $licenses
10879
		 * @param string|bool         $plugin_slug
10880
		 */
10881
		private function _update_licenses( $licenses, $plugin_slug = false ) {
10882
			$this->_logger->entrance();
10883
10884
			if ( is_array( $licenses ) ) {
10885
				for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) {
10886
					$licenses[ $i ]->updated = time();
10887
				}
10888
			}
10889
10890
			if ( ! is_string( $plugin_slug ) ) {
10891
				$this->_licenses = $licenses;
10892
			}
10893
10894
			$this->_store_licenses( true, $plugin_slug, $licenses );
10895
		}
10896
10897
		/**
10898
		 * @author Vova Feldman (@svovaf)
10899
		 * @since  1.0.4
10900
		 *
10901
		 * @param bool|number $plugin_id
10902
		 * @param bool        $flush Since 1.1.7.3
10903
		 * @param int         $expiration Since 1.2.2.7
10904
		 *
10905
		 * @return object|false New plugin tag info if exist.
10906
		 */
10907
		private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
10908
			$latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration );
10909
10910
			if ( ! is_object( $latest_tag ) ) {
10911
				return false;
10912
			}
10913
10914
			// Check if version is actually newer.
10915
			$has_new_version =
10916
				// If it's an non-installed add-on then always return latest.
10917
				( $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 10907 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...
10918
				// Compare versions.
10919
				version_compare( $this->get_plugin_version(), $latest_tag->version, '<' );
10920
10921
			$this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' );
10922
10923
			return $has_new_version ? $latest_tag : false;
10924
		}
10925
10926
		/**
10927
		 * @author Vova Feldman (@svovaf)
10928
		 * @since  1.0.5
10929
		 *
10930
		 * @param bool|number $plugin_id
10931
		 * @param bool        $flush      Since 1.1.7.3
10932
		 * @param int         $expiration Since 1.2.2.7
10933
		 *
10934
		 * @return bool|FS_Plugin_Tag
10935
		 */
10936
		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...
10937
			$this->_logger->entrance();
10938
10939
			if ( ! is_numeric( $plugin_id ) ) {
10940
				$plugin_id = $this->_plugin->id;
10941
			}
10942
10943
			$this->check_updates( true, $plugin_id, $flush, $expiration );
10944
			$updates = $this->get_all_updates();
10945
10946
			return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
10947
		}
10948
10949
		/**
10950
		 * Check if site assigned with active license.
10951
		 *
10952
		 * @author     Vova Feldman (@svovaf)
10953
		 * @since      1.0.6
10954
		 *
10955
		 * @deprecated Please use has_active_valid_license() instead because license can be cancelled.
10956
		 */
10957
		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...
10958
			return (
10959
				is_object( $this->_license ) &&
10960
				is_numeric( $this->_license->id ) &&
10961
				! $this->_license->is_expired()
10962
			);
10963
		}
10964
10965
		/**
10966
		 * Check if site assigned with active & valid (not expired) license.
10967
		 *
10968
		 * @author Vova Feldman (@svovaf)
10969
		 * @since  1.2.1
10970
		 */
10971
		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...
10972
			return (
10973
				is_object( $this->_license ) &&
10974
				is_numeric( $this->_license->id ) &&
10975
				$this->_license->is_active() &&
10976
				$this->_license->is_valid()
10977
			);
10978
		}
10979
10980
		/**
10981
		 * Check if site assigned with license with enabled features.
10982
		 *
10983
		 * @author Vova Feldman (@svovaf)
10984
		 * @since  1.0.6
10985
		 *
10986
		 * @return bool
10987
		 */
10988
		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...
10989
			return (
10990
				is_object( $this->_license ) &&
10991
				is_numeric( $this->_license->id ) &&
10992
				$this->_license->is_features_enabled()
10993
			);
10994
		}
10995
10996
		/**
10997
		 * Check if user is a trial or have feature enabled license.
10998
		 *
10999
		 * @author Vova Feldman (@svovaf)
11000
		 * @since  1.1.7
11001
		 *
11002
		 * @return bool
11003
		 */
11004
		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...
11005
			return $this->is_trial() || $this->has_features_enabled_license();
11006
		}
11007
11008
		/**
11009
		 * Checks if the current user can activate plugins or switch themes. Note that this method should only be used
11010
		 * after the `init` action is triggered because it is using `current_user_can()` which is only functional after
11011
		 * the context user is authenticated.
11012
		 *
11013
		 * @author Leo Fajardo (@leorw)
11014
		 * @since  1.2.2
11015
		 *
11016
		 * @return bool
11017
		 */
11018
		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...
11019
			return ( $this->is_plugin() && current_user_can( 'activate_plugins' ) )
11020
			       || ( $this->is_theme() && current_user_can( 'switch_themes' ) );
11021
		}
11022
11023
		/**
11024
		 * Sync site's plan.
11025
		 *
11026
		 * @author Vova Feldman (@svovaf)
11027
		 * @since  1.0.3
11028
		 *
11029
		 * @uses   FS_Api
11030
		 *
11031
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
11032
		 *                         the admin.
11033
		 */
11034
		private function _sync_license( $background = false ) {
11035
			$this->_logger->entrance();
11036
11037
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
11038
11039
			$is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() );
11040
11041
			if ( $is_addon_sync ) {
11042
				$this->_sync_addon_license( $plugin_id, $background );
11043
			} else {
11044
				$this->_sync_plugin_license( $background );
11045
			}
11046
11047
			$this->do_action( 'after_account_plan_sync', $this->_site->plan->name );
11048
		}
11049
11050
		/**
11051
		 * Sync plugin's add-on license.
11052
		 *
11053
		 * @author Vova Feldman (@svovaf)
11054
		 * @since  1.0.6
11055
		 * @uses   FS_Api
11056
		 *
11057
		 * @param number $addon_id
11058
		 * @param bool   $background
11059
		 */
11060
		private function _sync_addon_license( $addon_id, $background ) {
11061
			$this->_logger->entrance();
11062
11063
			if ( $this->is_addon_activated( $addon_id ) ) {
11064
				// If already installed, use add-on sync.
11065
				$fs_addon = self::get_instance_by_id( $addon_id );
11066
				$fs_addon->_sync_license( $background );
11067
11068
				return;
11069
			}
11070
11071
			// Validate add-on exists.
11072
			$addon = $this->get_addon( $addon_id );
11073
11074
			if ( ! is_object( $addon ) ) {
11075
				return;
11076
			}
11077
11078
			// Add add-on into account add-ons.
11079
			$account_addons = $this->get_account_addons();
11080
			if ( ! is_array( $account_addons ) ) {
11081
				$account_addons = array();
11082
			}
11083
			$account_addons[] = $addon->id;
11084
			$account_addons   = array_unique( $account_addons );
11085
			$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...
11086
11087
			// Load add-on licenses.
11088
			$licenses = $this->_fetch_licenses( $addon->id );
11089
11090
			// Sync add-on licenses.
11091
			if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) {
11092
				$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 11088 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...
11093
11094
				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 11088 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...
11095
					$plans_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$addon_id}/plans.json" );
11096
11097
					if ( ! isset( $plans_result->error ) ) {
11098
						$plans = array();
11099
						foreach ( $plans_result->plans as $plan ) {
11100
							$plans[] = new FS_Plugin_Plan( $plan );
11101
						}
11102
11103
						$this->_admin_notices->add_sticky(
11104
							sprintf(
11105
								( FS_Plan_Manager::instance()->has_free_plan( $plans ) ?
11106
									$this->get_text_inline( 'Your %s Add-on plan was successfully upgraded.', 'addon-successfully-upgraded-message' ) :
11107
									/* translators: %s:product name, e.g. Facebook add-on was successfully... */
11108
									$this->get_text_inline( '%s Add-on was successfully purchased.', 'addon-successfully-purchased-message' ) ),
11109
								$addon->title
11110
							) . ' ' . $this->get_latest_download_link(
11111
								$this->get_text_inline( 'Download the latest version', 'download-latest-version' ),
11112
								$addon_id
11113
							),
11114
							'addon_plan_upgraded_' . $addon->slug,
11115
							$this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!'
11116
						);
11117
					}
11118
				}
11119
			}
11120
		}
11121
11122
		/**
11123
		 * Sync site's plugin plan.
11124
		 *
11125
		 * @author Vova Feldman (@svovaf)
11126
		 * @since  1.0.6
11127
		 * @uses   FS_Api
11128
		 *
11129
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
11130
		 *                         the admin.
11131
		 */
11132
		private function _sync_plugin_license( $background = false ) {
11133
			$this->_logger->entrance();
11134
11135
			/**
11136
			 * Sync site info.
11137
			 *
11138
			 * @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.
11139
			 */
11140
			$site = $this->send_install_update( array(), true );
11141
11142
			$plan_change = 'none';
11143
11144
			if ( ! $this->is_api_result_entity( $site ) ) {
11145
				// Show API messages only if not background sync or if paying customer.
11146
				if ( ! $background || $this->is_paying() ) {
11147
					// Try to ping API to see if not blocked.
11148
					if ( ! FS_Api::test() ) {
11149
						/**
11150
						 * Failed to ping API - blocked!
11151
						 *
11152
						 * @author Vova Feldman (@svovaf)
11153
						 * @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.
11154
						 */
11155
						$api = $this->get_api_site_scope();
11156
11157
						if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) {
11158
							self::$_global_admin_notices->add(
11159
								sprintf(
11160
									$this->get_text_x_inline( 'Your server is blocking the access to Freemius\' API, which is crucial for %1s synchronization. Please contact your host to whitelist %2s', '%1s - plugin title, %2s - API domain', 'server-blocking-access' ),
11161
									$this->get_plugin_name(),
11162
									'<a href="' . $api->get_url() . '" target="_blank">' . $api->get_url() . '</a>'
11163
								) . '<br> ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $site->error, true ),
11164
								$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
11165
								'error',
11166
								$background,
11167
								false,
11168
								'api_blocked'
11169
							);
11170
						}
11171
					} else {
11172
						// Authentication params are broken.
11173
						$this->_admin_notices->add(
11174
							$this->get_text_inline( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.', 'wrong-authentication-param-message' ),
11175
							$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
11176
							'error'
11177
						);
11178
					}
11179
				}
11180
11181
				// No reason to continue with license sync while there are API issues.
11182
				return;
11183
			}
11184
11185
			// Remove sticky API connectivity message.
11186
			self::$_global_admin_notices->remove_sticky( 'api_blocked' );
11187
11188
			$site = new FS_Site( $site );
0 ignored issues
show
Bug introduced by
It seems like $site defined by new \FS_Site($site) on line 11188 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...
11189
11190
			// Sync plans.
11191
			$this->_sync_plans();
11192
11193
			if ( ! $this->has_paid_plan() ) {
11194
				$this->_site = $site;
11195
				$this->_enrich_site_plan( true );
11196
				$this->_store_site();
11197
			} else {
11198
				/**
11199
				 * Sync licenses. Pass the site's license ID so that the foreign licenses will be fetched if the license
11200
				 * associated with that ID is not included in the user's licenses collection.
11201
				 */
11202
				$this->_sync_licenses( $site->license_id );
11203
11204
				// Check if plan / license changed.
11205
				if ( ! FS_Entity::equals( $site->plan, $this->_site->plan ) ||
11206
				     // Check if trial started.
11207
				     $site->trial_plan_id != $this->_site->trial_plan_id ||
11208
				     $site->trial_ends != $this->_site->trial_ends ||
11209
				     // Check if license changed.
11210
				     $site->license_id != $this->_site->license_id
11211
				) {
11212
					if ( $site->is_trial() && ( ! $this->_site->is_trial() || $site->trial_ends != $this->_site->trial_ends ) ) {
11213
						// New trial started.
11214
						$this->_site = $site;
11215
						$plan_change = 'trial_started';
11216
11217
						// Store trial plan information.
11218
						$this->_enrich_site_trial_plan( true );
11219
11220
						// For trial with subscription use-case.
11221
						$new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id );
11222
11223
						if ( is_object( $new_license ) && $new_license->is_valid() ) {
11224
							$this->_site = $site;
11225
							$this->_update_site_license( $new_license );
11226
							$this->_store_licenses();
11227
							$this->_enrich_site_plan( true );
11228
11229
							$this->_sync_site_subscription( $this->_license );
11230
						}
11231
					} else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) {
11232
						// Was in trial, but now trial expired and no license ID.
11233
						// New trial started.
11234
						$this->_site = $site;
11235
						$plan_change = 'trial_expired';
11236
11237
						// Clear trial plan information.
11238
						$this->_storage->trial_plan = null;
11239
11240
					} else {
11241
						$is_free = $this->is_free_plan();
11242
11243
						// Make sure license exist and not expired.
11244
						$new_license = is_null( $site->license_id ) ?
11245
							null :
11246
							$this->_get_license_by_id( $site->license_id );
11247
11248
						if ( $is_free && is_null( $new_license ) && $this->has_any_license() && $this->_license->is_cancelled ) {
11249
							// License cancelled.
11250
							$this->_site = $site;
11251
							$this->_update_site_license( $new_license );
11252
							$this->_store_licenses();
11253
							$this->_enrich_site_plan( true );
11254
11255
							$plan_change = 'cancelled';
11256
						} 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...
11257
							// The license is expired, so ignore upgrade method.
11258
						} else {
11259
							// License changed.
11260
							$this->_site = $site;
11261
							$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 11244 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...
11262
							$this->_store_licenses();
11263
							$this->_enrich_site_plan( true );
11264
11265
							$plan_change = $is_free ?
11266
								'upgraded' :
11267
								( is_object( $new_license ) ?
11268
									'changed' :
11269
									'downgraded' );
11270
						}
11271
					}
11272
11273
					// Store updated site info.
11274
					$this->_store_site();
11275
				} else {
11276
					if ( is_object( $this->_license ) && $this->_license->is_expired() ) {
11277
						if ( ! $this->has_features_enabled_license() ) {
11278
							$this->_deactivate_license();
11279
							$plan_change = 'downgraded';
11280
						} else {
11281
							$plan_change = 'expired';
11282
						}
11283
					}
11284
11285
					if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) {
11286
						$this->_sync_site_subscription( $this->_license );
11287
					}
11288
				}
11289
			}
11290
11291
			$hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...';
11292
11293
			if ( $this->has_paid_plan() ) {
11294
				switch ( $plan_change ) {
11295
					case 'none':
11296
						if ( ! $background && is_admin() ) {
11297
							$plan = $this->is_trial() ?
11298
								$this->_storage->trial_plan :
11299
								$this->_site->plan;
11300
11301
							if ( $plan->is_free() ) {
11302
								$this->_admin_notices->add(
11303
									sprintf(
11304
										$this->get_text_inline( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.', 'plan-did-not-change-message' ),
11305
										'<i><b>' . $plan->title . ( $this->is_trial() ? ' ' . $this->get_text_x_inline( 'Trial', 'trial period', 'trial' ) : '' ) . '</b></i>'
11306
									) . ' ' . sprintf(
11307
										'<a href="%s">%s</a>',
11308
										$this->contact_url(
11309
											'bug',
11310
											sprintf( $this->get_text_inline( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.', 'plan-did-not-change-email-message' ),
11311
												strtoupper( $plan->name )
11312
											)
11313
										),
11314
										$this->get_text_inline( 'Please contact us here', 'contact-us-here' )
11315
									),
11316
									$hmm_text
11317
								);
11318
							}
11319
						}
11320
						break;
11321
					case 'upgraded':
11322
						$this->_admin_notices->add_sticky(
11323
							sprintf(
11324
								$this->get_text_inline( 'Your plan was successfully upgraded.', 'plan-upgraded-message' ),
11325
								'<i>' . $this->get_plugin_name() . '</i>'
11326
							) . $this->get_complete_upgrade_instructions(),
11327
							'plan_upgraded',
11328
							$this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!'
11329
						);
11330
11331
						$this->_admin_notices->remove_sticky( array(
11332
							'trial_started',
11333
							'trial_promotion',
11334
							'trial_expired',
11335
							'activation_complete',
11336
						) );
11337
						break;
11338
					case 'changed':
11339
						$this->_admin_notices->add_sticky(
11340
							sprintf(
11341
								$this->get_text_inline( 'Your plan was successfully changed to %s.', 'plan-changed-to-x-message' ),
11342
								$this->_site->plan->title
11343
							),
11344
							'plan_changed'
11345
						);
11346
11347
						$this->_admin_notices->remove_sticky( array(
11348
							'trial_started',
11349
							'trial_promotion',
11350
							'trial_expired',
11351
							'activation_complete',
11352
						) );
11353
						break;
11354
					case 'downgraded':
11355
						$this->_admin_notices->add_sticky(
11356
							sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using the free %s forever.', 'license-expired-blocking-message' ), $this->_module_type ),
11357
							'license_expired',
11358
							$hmm_text
11359
						);
11360
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
11361
						break;
11362
					case 'cancelled':
11363
						$this->_admin_notices->add(
11364
							$this->get_text_inline( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.', 'license-cancelled' ) . ' ' .
11365
							sprintf(
11366
								'<a href="%s">%s</a>',
11367
								$this->contact_url( 'bug' ),
11368
								$this->get_text_inline( 'Please contact us here', 'contact-us-here' )
11369
							),
11370
							$hmm_text,
11371
							'error'
11372
						);
11373
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
11374
						break;
11375
					case 'expired':
11376
						$this->_admin_notices->add_sticky(
11377
							sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.', 'license-expired-non-blocking-message' ), $this->_site->plan->title ),
11378
							'license_expired',
11379
							$hmm_text
11380
						);
11381
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
11382
						break;
11383
					case 'trial_started':
11384
						$this->_admin_notices->add_sticky(
11385
							sprintf(
11386
								$this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ),
11387
								'<i>' . $this->get_plugin_name() . '</i>'
11388
							) . $this->get_complete_upgrade_instructions( $this->_storage->trial_plan->title ),
11389
							'trial_started',
11390
							$this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!'
11391
						);
11392
11393
						$this->_admin_notices->remove_sticky( array(
11394
							'trial_promotion',
11395
						) );
11396
						break;
11397
					case 'trial_expired':
11398
						$this->_admin_notices->add_sticky(
11399
							$this->get_text_inline( 'Your trial has expired. You can still continue using all our free features.', 'trial-expired-message' ),
11400
							'trial_expired',
11401
							$hmm_text
11402
						);
11403
						$this->_admin_notices->remove_sticky( array(
11404
							'trial_started',
11405
							'trial_promotion',
11406
							'plan_upgraded',
11407
						) );
11408
						break;
11409
				}
11410
			}
11411
11412
			if ( 'none' !== $plan_change ) {
11413
				$this->do_action( 'after_license_change', $plan_change, $this->_site->plan );
11414
			}
11415
		}
11416
11417
		/**
11418
		 * @author Vova Feldman (@svovaf)
11419
		 * @since  1.0.5
11420
		 *
11421
		 * @param bool $background
11422
		 */
11423
		protected function _activate_license( $background = false ) {
11424
			$this->_logger->entrance();
11425
11426
			$license_id = fs_request_get( 'license_id' );
11427
11428
			if ( FS_Plugin_License::is_valid_id( $license_id ) && $license_id == $this->_site->license_id ) {
11429
				// License is already activated.
11430
				return;
11431
			}
11432
11433
			$premium_license = FS_Plugin_License::is_valid_id( $license_id ) ?
11434
				$this->_get_license_by_id( $license_id ) :
11435
				$this->_get_available_premium_license();
11436
11437
			if ( ! is_object( $premium_license ) ) {
11438
				return;
11439
			}
11440
11441
			/**
11442
			 * If the premium license is already associated with the install, just
11443
			 * update the license reference (activation is not required).
11444
			 *
11445
			 * @since 1.1.9
11446
			 */
11447
			if ( $premium_license->id == $this->_site->license_id ) {
11448
				// License is already activated.
11449
				$this->_update_site_license( $premium_license );
11450
				$this->_enrich_site_plan( false );
11451
				$this->_store_account();
11452
11453
				return;
11454
			}
11455
11456
			if ( $this->_site->user_id != $premium_license->user_id ) {
11457
				$api_request_params = array( 'license_key' => $premium_license->secret_key );
11458
			} else {
11459
				$api_request_params = array();
11460
			}
11461
11462
			$api     = $this->get_api_site_scope();
11463
			$license = $api->call( "/licenses/{$premium_license->id}.json", 'put', $api_request_params );
11464
11465
			if ( ! $this->is_api_result_entity( $license ) ) {
11466
				if ( ! $background ) {
11467
					$this->_admin_notices->add( sprintf(
11468
						'%s %s',
11469
						$this->get_text_inline( 'It looks like the license could not be activated.', 'license-activation-failed-message' ),
11470
						( is_object( $license ) && isset( $license->error ) ?
11471
							$license->error->message :
11472
							sprintf( '%s<br><code>%s</code>',
11473
								$this->get_text_inline( 'Error received from the server:', 'server-error-message' ),
11474
								var_export( $license, true )
11475
							)
11476
						)
11477
					),
11478
						$this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...',
11479
						'error'
11480
					);
11481
				}
11482
11483
				return;
11484
			}
11485
			$premium_license = new FS_Plugin_License( $license );
11486
11487
			// Updated site plan.
11488
			$site = $this->get_api_site_scope()->get( '/', true );
11489
			if ( $this->is_api_result_entity( $site ) ) {
11490
				$this->_site = new FS_Site( $site );
11491
			}
11492
			$this->_update_site_license( $premium_license );
11493
			$this->_enrich_site_plan( false );
11494
11495
			$this->_store_account();
11496
11497
			if ( ! $background ) {
11498
				$this->_admin_notices->add_sticky(
11499
					$this->get_text_inline( 'Your license was successfully activated.', 'license-activated-message' ) .
11500
					$this->get_complete_upgrade_instructions(),
11501
					'license_activated',
11502
					$this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!'
11503
				);
11504
			}
11505
11506
			$this->_admin_notices->remove_sticky( array(
11507
				'trial_promotion',
11508
				'license_expired',
11509
			) );
11510
		}
11511
11512
		/**
11513
		 * @author Vova Feldman (@svovaf)
11514
		 * @since  1.0.5
11515
		 *
11516
		 * @param bool $show_notice
11517
		 */
11518
		protected function _deactivate_license( $show_notice = true ) {
11519
			$this->_logger->entrance();
11520
11521
			$hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...';
11522
11523
			if ( ! is_object( $this->_license ) ) {
11524
				$this->_admin_notices->add(
11525
					sprintf( $this->get_text_inline( 'It looks like your site currently doesn\'t have an active license.', 'no-active-license-message' ), $this->_site->plan->title ),
11526
					$hmm_text
11527
				);
11528
11529
				return;
11530
			}
11531
11532
			$api     = $this->get_api_site_scope();
11533
			$license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' );
11534
11535
			if ( isset( $license->error ) ) {
11536
				$this->_admin_notices->add(
11537
					$this->get_text_inline( 'It looks like the license deactivation failed.', 'license-deactivation-failed-message' ) . '<br> ' .
11538
					$this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . ' ' . var_export( $license->error, true ),
11539
					$hmm_text,
11540
					'error'
11541
				);
11542
11543
				return;
11544
			}
11545
11546
			// Update license cache.
11547
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
11548
				if ( $license->id == $this->_licenses[ $i ]->id ) {
11549
					$this->_licenses[ $i ] = new FS_Plugin_License( $license );
11550
				}
11551
			}
11552
11553
			// Updated site plan to default.
11554
			$this->_sync_plans();
11555
			$this->_site->plan->id = $this->_plans[0]->id;
11556
			// Unlink license from site.
11557
			$this->_update_site_license( null );
11558
			$this->_enrich_site_plan( false );
11559
11560
			$this->_store_account();
11561
11562
			if ( $show_notice ) {
11563
				$this->_admin_notices->add(
11564
					sprintf( $this->get_text_inline( 'Your license was successfully deactivated, you are back to the %s plan.', 'license-deactivation-message' ), $this->_site->plan->title ),
11565
					$this->get_text_inline( 'O.K', 'ok' )
11566
				);
11567
			}
11568
11569
			$this->_admin_notices->remove_sticky( array(
11570
				'plan_upgraded',
11571
				'license_activated',
11572
			) );
11573
		}
11574
11575
		/**
11576
		 * Site plan downgrade.
11577
		 *
11578
		 * @author Vova Feldman (@svovaf)
11579
		 * @since  1.0.4
11580
		 *
11581
		 * @uses   FS_Api
11582
		 */
11583
		private function _downgrade_site() {
11584
			$this->_logger->entrance();
11585
11586
			$api  = $this->get_api_site_scope();
11587
			$site = $api->call( 'downgrade.json', 'put' );
11588
11589
			$plan_downgraded = false;
11590
			$plan            = false;
11591
			if ( $this->is_api_result_entity( $site ) ) {
11592
				$prev_plan_id = $this->_site->plan->id;
11593
11594
				// Update new site plan id.
11595
				$this->_site->plan->id = $site->plan_id;
11596
11597
				$plan         = $this->_enrich_site_plan();
11598
				$subscription = $this->_sync_site_subscription( $this->_license );
11599
11600
				// Plan downgraded if plan was changed or subscription was cancelled.
11601
				$plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) ||
11602
				                   ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() );
11603
			} 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...
11604
				// handle different error cases.
11605
11606
			}
11607
11608
			if ( $plan_downgraded ) {
11609
				// Remove previous sticky message about upgrade (if exist).
11610
				$this->_admin_notices->remove_sticky( 'plan_upgraded' );
11611
11612
				$this->_admin_notices->add(
11613
					sprintf( $this->get_text_inline( 'Your plan was successfully downgraded. Your %s plan license will expire in %s.', 'plan-x-downgraded-message' ),
11614
						$plan->title,
11615
						human_time_diff( time(), strtotime( $this->_license->expiration ) )
11616
					)
11617
				);
11618
11619
				// Store site updates.
11620
				$this->_store_site();
11621
			} else {
11622
				$this->_admin_notices->add(
11623
					$this->get_text_inline( 'Seems like we are having some temporary issue with your plan downgrade. Please try again in few minutes.', 'plan-downgraded-failure-message' ),
11624
					$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
11625
					'error'
11626
				);
11627
			}
11628
		}
11629
11630
		/**
11631
		 * @author Vova Feldman (@svovaf)
11632
		 * @since  1.1.8.1
11633
		 *
11634
		 * @param bool|string $plan_name
11635
		 *
11636
		 * @return bool If trial was successfully started.
11637
		 */
11638
		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...
11639
			$this->_logger->entrance();
11640
11641
			// Alias.
11642
			$oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...';
11643
11644
			if ( $this->is_trial() ) {
11645
				// Already in trial mode.
11646
				$this->_admin_notices->add(
11647
					sprintf( $this->get_text_inline( 'You are already running the %s in a trial mode.', 'in-trial-mode' ), $this->_module_type ),
11648
					$oops_text,
11649
					'error'
11650
				);
11651
11652
				return false;
11653
			}
11654
11655
			if ( $this->_site->is_trial_utilized() ) {
11656
				// Trial was already utilized.
11657
				$this->_admin_notices->add(
11658
					$this->get_text_inline( 'You already utilized a trial before.', 'trial-utilized' ),
11659
					$oops_text,
11660
					'error'
11661
				);
11662
11663
				return false;
11664
			}
11665
11666
			if ( false !== $plan_name ) {
11667
				$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 11638 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...
11668
11669
				if ( false === $plan ) {
11670
					// Plan doesn't exist.
11671
					$this->_admin_notices->add(
11672
						sprintf( $this->get_text_inline( 'Plan %s do not exist, therefore, can\'t start a trial.', 'trial-plan-x-not-exist' ), $plan_name ),
11673
						$oops_text,
11674
						'error'
11675
					);
11676
11677
					return false;
11678
				}
11679
11680
				if ( ! $plan->has_trial() ) {
11681
					// Plan doesn't exist.
11682
					$this->_admin_notices->add(
11683
						sprintf( $this->get_text_inline( 'Plan %s does not support a trial period.', 'plan-x-no-trial' ), $plan_name ),
11684
						$oops_text,
11685
						'error'
11686
					);
11687
11688
					return false;
11689
				}
11690
			} else {
11691
				if ( ! $this->has_trial_plan() ) {
11692
					// None of the plans have a trial.
11693
					$this->_admin_notices->add(
11694
						sprintf( $this->get_text_inline( 'None of the %s\'s plans supports a trial period.', 'no-trials' ), $this->_module_type ),
11695
						$oops_text,
11696
						'error'
11697
					);
11698
11699
					return false;
11700
				}
11701
11702
				$plans_with_trial = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
11703
11704
				$plan = $plans_with_trial[0];
11705
			}
11706
11707
			$api  = $this->get_api_site_scope();
11708
			$plan = $api->call( "plans/{$plan->id}/trials.json", 'post' );
11709
11710
			if ( ! $this->is_api_result_entity( $plan ) ) {
11711
				// Some API error while trying to start the trial.
11712
				$this->_admin_notices->add(
11713
					sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type )
11714
					. ' ' . var_export( $plan, true ),
11715
					$oops_text,
11716
					'error'
11717
				);
11718
11719
				return false;
11720
			}
11721
11722
			// Sync license.
11723
			$this->_sync_license();
11724
11725
			return $this->is_trial();
11726
		}
11727
11728
		/**
11729
		 * Cancel site trial.
11730
		 *
11731
		 * @author Vova Feldman (@svovaf)
11732
		 * @since  1.0.9
11733
		 *
11734
		 * @uses   FS_Api
11735
		 */
11736
		private function _cancel_trial() {
11737
			$this->_logger->entrance();
11738
11739
			// Alias.
11740
			$oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...';
11741
11742
			if ( ! $this->is_trial() ) {
11743
				$this->_admin_notices->add(
11744
					$this->get_text_inline( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)', 'trial-cancel-no-trial-message' ),
11745
					$oops_text,
11746
					'error'
11747
				);
11748
11749
				return;
11750
			}
11751
11752
			$api  = $this->get_api_site_scope();
11753
			$site = $api->call( 'trials.json', 'delete' );
11754
11755
			$trial_cancelled = false;
11756
11757
			if ( $this->is_api_result_entity( $site ) ) {
11758
				$prev_trial_ends = $this->_site->trial_ends;
11759
11760
				if ( $this->is_paid_trial() ) {
11761
					$this->_license->expiration   = $site->trial_ends;
11762
					$this->_license->is_cancelled = true;
11763
					$this->_update_site_license( $this->_license );
11764
					$this->_store_licenses();
11765
11766
					// Clear subscription reference.
11767
					$this->_sync_site_subscription( null );
11768
				}
11769
11770
				// Update site info.
11771
				$this->_site = new FS_Site( $site );
11772
				$this->_enrich_site_plan();
11773
11774
				$trial_cancelled = ( $prev_trial_ends != $site->trial_ends );
11775
			} 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...
11776
				// handle different error cases.
11777
11778
			}
11779
11780
			if ( $trial_cancelled ) {
11781
				// Remove previous sticky messages about upgrade or trial (if exist).
11782
				$this->_admin_notices->remove_sticky( array(
11783
					'trial_started',
11784
					'trial_promotion',
11785
					'plan_upgraded',
11786
				) );
11787
11788
				// Store site updates.
11789
				$this->_store_site();
11790
11791
				if ( ! $this->is_addon() ||
11792
				     ! $this->deactivate_premium_only_addon_without_license( true )
11793
				) {
11794
					$this->_admin_notices->add(
11795
						sprintf( $this->get_text_inline( 'Your %s free trial was successfully cancelled.', 'trial-cancel-message' ), $this->_storage->trial_plan->title )
11796
					);
11797
				}
11798
11799
				// Clear trial plan information.
11800
				unset( $this->_storage->trial_plan );
11801
			} else {
11802
				$this->_admin_notices->add(
11803
					$this->get_text_inline( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.', 'trial-cancel-failure-message' ),
11804
					$oops_text,
11805
					'error'
11806
				);
11807
			}
11808
		}
11809
11810
		/**
11811
		 * @author Vova Feldman (@svovaf)
11812
		 * @since  1.0.6
11813
		 *
11814
		 * @param bool|number $plugin_id
11815
		 *
11816
		 * @return bool
11817
		 */
11818
		private function _is_addon_id( $plugin_id ) {
11819
			return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id );
11820
		}
11821
11822
		/**
11823
		 * Check if user eligible to download premium version updates.
11824
		 *
11825
		 * @author Vova Feldman (@svovaf)
11826
		 * @since  1.0.6
11827
		 *
11828
		 * @return bool
11829
		 */
11830
		private function _can_download_premium() {
11831
			return $this->has_active_valid_license() ||
11832
			       ( $this->is_trial() && ! $this->get_trial_plan()->is_free() );
11833
		}
11834
11835
		/**
11836
		 *
11837
		 * @author Vova Feldman (@svovaf)
11838
		 * @since  1.0.6
11839
		 *
11840
		 * @param bool|number $addon_id
11841
		 * @param string      $type "json" or "zip"
11842
		 *
11843
		 * @return string
11844
		 */
11845
		private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) {
11846
11847
			$is_addon = $this->_is_addon_id( $addon_id );
11848
11849
			$is_premium = null;
11850
			if ( ! $is_addon ) {
11851
				$is_premium = $this->_can_download_premium();
11852
			} 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 11845 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...
11853
				$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 11845 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...
11854
			}
11855
11856
			// If add-on, then append add-on ID.
11857
			$endpoint = ( $is_addon ? "/addons/$addon_id" : '' ) .
11858
			            '/updates/latest.' . $type;
11859
11860
			// If add-on and not yet activated, try to fetch based on server licensing.
11861
			if ( is_bool( $is_premium ) ) {
11862
				$endpoint = add_query_arg( 'is_premium', json_encode( $is_premium ), $endpoint );
11863
			}
11864
11865
			if ( $this->has_secret_key() ) {
11866
				$endpoint = add_query_arg( 'type', 'all', $endpoint );
11867
			}
11868
11869
			return $endpoint;
11870
		}
11871
11872
		/**
11873
		 * @author Vova Feldman (@svovaf)
11874
		 * @since  1.0.4
11875
		 *
11876
		 * @param bool|number $addon_id
11877
		 * @param bool        $flush      Since 1.1.7.3
11878
		 * @param int         $expiration Since 1.2.2.7
11879
		 *
11880
		 * @return object|false Plugin latest tag info.
11881
		 */
11882
		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...
11883
			$this->_logger->entrance();
11884
11885
			/**
11886
			 * @since 1.1.7.3 Check for plugin updates from Freemius only if opted-in.
11887
			 * @since 1.1.7.4 Also check updates for add-ons.
11888
			 */
11889
			if ( ! $this->is_registered() &&
11890
			     ! $this->_is_addon_id( $addon_id )
11891
			) {
11892
				return false;
11893
			}
11894
11895
			$tag = $this->get_api_site_or_plugin_scope()->get(
11896
				$this->_get_latest_version_endpoint( $addon_id, 'json' ),
11897
				$flush,
11898
				$expiration
11899
			);
11900
11901
			$latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get';
11902
11903
			$this->_logger->departure( 'Latest version ' . $latest_version );
11904
11905
			return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false;
11906
		}
11907
11908
		#----------------------------------------------------------------------------------
11909
		#region Download Plugin
11910
		#----------------------------------------------------------------------------------
11911
11912
		/**
11913
		 * Download latest plugin version, based on plan.
11914
		 *
11915
		 * Not like _download_latest(), this will redirect the page
11916
		 * to secure download url to prevent dual download (from FS to WP server,
11917
		 * and then from WP server to the client / browser).
11918
		 *
11919
		 * @author Vova Feldman (@svovaf)
11920
		 * @since  1.0.9
11921
		 *
11922
		 * @param bool|number $plugin_id
11923
		 *
11924
		 * @uses   FS_Api
11925
		 * @uses   wp_redirect()
11926
		 */
11927
		private function download_latest_directly( $plugin_id = false ) {
11928
			$this->_logger->entrance();
11929
11930
			wp_redirect( $this->get_latest_download_api_url( $plugin_id ) );
11931
		}
11932
11933
		/**
11934
		 * Get latest plugin FS API download URL.
11935
		 *
11936
		 * @author Vova Feldman (@svovaf)
11937
		 * @since  1.0.9
11938
		 *
11939
		 * @param bool|number $plugin_id
11940
		 *
11941
		 * @return string
11942
		 */
11943
		private function get_latest_download_api_url( $plugin_id = false ) {
11944
			$this->_logger->entrance();
11945
11946
			return $this->get_api_site_scope()->get_signed_url(
11947
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
11948
			);
11949
		}
11950
11951
		/**
11952
		 * Get payment invoice URL.
11953
		 *
11954
		 * @author Vova Feldman (@svovaf)
11955
		 * @since  1.2.0
11956
		 *
11957
		 * @param bool|number $payment_id
11958
		 *
11959
		 * @return string
11960
		 */
11961
		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...
11962
			$this->_logger->entrance();
11963
11964
			return $this->get_api_user_scope()->get_signed_url(
11965
				"/payments/{$payment_id}/invoice.pdf"
11966
			);
11967
		}
11968
11969
		/**
11970
		 * Get latest plugin download link.
11971
		 *
11972
		 * @author Vova Feldman (@svovaf)
11973
		 * @since  1.0.9
11974
		 *
11975
		 * @param string      $label
11976
		 * @param bool|number $plugin_id
11977
		 *
11978
		 * @return string
11979
		 */
11980
		private function get_latest_download_link( $label, $plugin_id = false ) {
11981
			return sprintf(
11982
				'<a target="_blank" href="%s">%s</a>',
11983
				$this->_get_latest_download_local_url( $plugin_id ),
11984
				$label
11985
			);
11986
		}
11987
11988
		/**
11989
		 * Get latest plugin download local URL.
11990
		 *
11991
		 * @author Vova Feldman (@svovaf)
11992
		 * @since  1.0.9
11993
		 *
11994
		 * @param bool|number $plugin_id
11995
		 *
11996
		 * @return string
11997
		 */
11998
		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...
11999
			// Add timestamp to protect from caching.
12000
			$params = array( 'ts' => WP_FS__SCRIPT_START_TIME );
12001
12002
			if ( ! empty( $plugin_id ) ) {
12003
				$params['plugin_id'] = $plugin_id;
12004
			}
12005
12006
			return $this->get_account_url( 'download_latest', $params );
12007
		}
12008
12009
		#endregion Download Plugin ------------------------------------------------------------------
12010
12011
		/**
12012
		 * @author Vova Feldman (@svovaf)
12013
		 * @since  1.0.4
12014
		 *
12015
		 * @uses   FS_Api
12016
		 *
12017
		 * @param bool        $background Hints the method if it's a background updates check. If false, it means that
12018
		 *                                was initiated by the admin.
12019
		 * @param bool|number $plugin_id
12020
		 * @param bool        $flush      Since 1.1.7.3
12021
		 * @param int         $expiration Since 1.2.2.7
12022
		 */
12023
		private function check_updates(
12024
			$background = false,
12025
			$plugin_id = false,
12026
			$flush = true,
12027
			$expiration = WP_FS__TIME_24_HOURS_IN_SEC
12028
		) {
12029
			$this->_logger->entrance();
12030
12031
			// Check if there's a newer version for download.
12032
			$new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration );
12033
12034
			$update = null;
12035
			if ( is_object( $new_version ) ) {
12036
				$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...
12037
12038
				if ( ! $background ) {
12039
					$this->_admin_notices->add(
12040
						sprintf(
12041
                            /* translators: %s: Numeric version number (e.g. '2.1.9' */
12042
							$this->get_text_inline( 'Version %s was released.', 'version-x-released' ) . ' ' . $this->get_text_inline( 'Please download %s.', 'please-download-x' ),
12043
							$update->version,
12044
							sprintf(
12045
								'<a href="%s" target="_blank">%s</a>',
12046
								$this->get_account_url( 'download_latest' ),
12047
								sprintf(
12048
                                    /* translators: %s: plan name (e.g. latest "Professional" version) */
12049
								    $this->get_text_inline( 'the latest %s version here', 'latest-x-version' ),
12050
                                    $this->_site->plan->title
12051
                                )
12052
							)
12053
						),
12054
						$this->get_text_inline( 'New', 'new' ) . '!'
12055
					);
12056
				}
12057
			} else if ( false === $new_version && ! $background ) {
12058
				$this->_admin_notices->add(
12059
					$this->get_text_inline( 'Seems like you got the latest release.', 'you-have-latest' ),
12060
					$this->get_text_inline( 'You are all good!', 'you-are-good' )
12061
				);
12062
			}
12063
12064
			$this->_store_update( $update, true, $plugin_id );
12065
		}
12066
12067
		/**
12068
		 * @author Vova Feldman (@svovaf)
12069
		 * @since  1.0.4
12070
		 *
12071
		 * @param bool $flush Since 1.1.7.3 add 24 hour cache by default.
12072
		 *
12073
		 * @return FS_Plugin[]
12074
		 *
12075
		 * @uses   FS_Api
12076
		 */
12077
		private function sync_addons( $flush = false ) {
12078
			$this->_logger->entrance();
12079
12080
			$api = $this->get_api_site_or_plugin_scope();
12081
12082
			/**
12083
			 * @since 1.2.1
12084
			 *
12085
			 * If there's a cached version of the add-ons and not asking
12086
			 * for a flush, just use the currently stored add-ons.
12087
			 */
12088
			if ( ! $flush && $api->is_cached( '/addons.json?enriched=true' ) ) {
12089
				$addons = self::get_all_addons();
12090
12091
				return $addons[ $this->_plugin->id ];
12092
			}
12093
12094
			$result = $api->get( '/addons.json?enriched=true', $flush );
12095
12096
			$addons = array();
12097
			if ( $this->is_api_result_object( $result, 'plugins' ) &&
12098
			     is_array( $result->plugins )
12099
			) {
12100
				for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) {
12101
					$addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] );
12102
				}
12103
12104
				$this->_store_addons( $addons, true );
12105
			}
12106
12107
			return $addons;
12108
		}
12109
12110
		/**
12111
		 * Handle user email update.
12112
		 *
12113
		 * @author Vova Feldman (@svovaf)
12114
		 * @since  1.0.3
12115
		 * @uses   FS_Api
12116
		 *
12117
		 * @param string $new_email
12118
		 *
12119
		 * @return object
12120
		 */
12121
		private function update_email( $new_email ) {
12122
			$this->_logger->entrance();
12123
12124
12125
			$api  = $this->get_api_user_scope();
12126
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array(
12127
				'email'                   => $new_email,
12128
				'after_email_confirm_url' => $this->_get_admin_page_url(
12129
					'account',
12130
					array( 'fs_action' => 'sync_user' )
12131
				),
12132
			) );
12133
12134
			if ( ! isset( $user->error ) ) {
12135
				$this->_user->email       = $user->email;
12136
				$this->_user->is_verified = $user->is_verified;
12137
				$this->_store_user();
12138
			} 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...
12139
				// handle different error cases.
12140
12141
			}
12142
12143
			return $user;
12144
		}
12145
12146
		#----------------------------------------------------------------------------------
12147
		#region API Error Handling
12148
		#----------------------------------------------------------------------------------
12149
12150
		/**
12151
		 * @author Vova Feldman (@svovaf)
12152
		 * @since  1.1.1
12153
		 *
12154
		 * @param mixed $result
12155
		 *
12156
		 * @return bool Is API result contains an error.
12157
		 */
12158
		private function is_api_error( $result ) {
12159
			return FS_Api::is_api_error( $result );
12160
		}
12161
12162
		/**
12163
		 * Checks if given API result is a non-empty and not an error object.
12164
		 *
12165
		 * @author Vova Feldman (@svovaf)
12166
		 * @since  1.2.1.5
12167
		 *
12168
		 * @param mixed       $result
12169
		 * @param string|null $required_property Optional property we want to verify that is set.
12170
		 *
12171
		 * @return bool
12172
		 */
12173
		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...
12174
			return FS_Api::is_api_result_object( $result, $required_property );
12175
		}
12176
12177
		/**
12178
		 * Checks if given API result is a non-empty entity object with non-empty ID.
12179
		 *
12180
		 * @author Vova Feldman (@svovaf)
12181
		 * @since  1.2.1.5
12182
		 *
12183
		 * @param mixed $result
12184
		 *
12185
		 * @return bool
12186
		 */
12187
		private function is_api_result_entity( $result ) {
12188
			return FS_Api::is_api_result_entity( $result );
12189
		}
12190
12191
		#endregion
12192
12193
		/**
12194
		 * Make sure a given argument is an array of a specific type.
12195
		 *
12196
		 * @author Vova Feldman (@svovaf)
12197
		 * @since  1.2.1.5
12198
		 *
12199
		 * @param mixed  $array
12200
		 * @param string $class
12201
		 *
12202
		 * @return bool
12203
		 */
12204
		private function is_array_instanceof( $array, $class ) {
12205
			return ( is_array( $array ) && ( empty( $array ) || $array[0] instanceof $class ) );
12206
		}
12207
12208
		/**
12209
		 * Start install ownership change.
12210
		 *
12211
		 * @author Vova Feldman (@svovaf)
12212
		 * @since  1.1.1
12213
		 * @uses   FS_Api
12214
		 *
12215
		 * @param string $candidate_email
12216
		 *
12217
		 * @return bool Is ownership change successfully initiated.
12218
		 */
12219
		private function init_change_owner( $candidate_email ) {
12220
			$this->_logger->entrance();
12221
12222
			$api    = $this->get_api_site_scope();
12223
			$result = $api->call( "/users/{$this->_user->id}.json", 'put', array(
12224
				'email'             => $candidate_email,
12225
				'after_confirm_url' => $this->_get_admin_page_url(
12226
					'account',
12227
					array( 'fs_action' => 'change_owner' )
12228
				),
12229
			) );
12230
12231
			return ! $this->is_api_error( $result );
12232
		}
12233
12234
		/**
12235
		 * Handle install ownership change.
12236
		 *
12237
		 * @author Vova Feldman (@svovaf)
12238
		 * @since  1.1.1
12239
		 * @uses   FS_Api
12240
		 *
12241
		 * @return bool Was ownership change successfully complete.
12242
		 */
12243
		private function complete_change_owner() {
12244
			$this->_logger->entrance();
12245
12246
			$site_result = $this->get_api_site_scope( true )->get();
12247
			$site        = new FS_Site( $site_result );
12248
			$this->_site = $site;
12249
12250
			$user     = new FS_User();
12251
			$user->id = fs_request_get( 'user_id' );
12252
12253
			// Validate install's user and given user.
12254
			if ( $user->id != $this->_site->user_id ) {
12255
				return false;
12256
			}
12257
12258
			$user->public_key = fs_request_get( 'user_public_key' );
12259
			$user->secret_key = fs_request_get( 'user_secret_key' );
12260
12261
			// Fetch new user information.
12262
			$this->_user = $user;
12263
			$user_result = $this->get_api_user_scope( true )->get();
12264
			$user        = new FS_User( $user_result );
12265
			$this->_user = $user;
12266
12267
			$this->_set_account( $user, $site );
12268
12269
			return true;
12270
		}
12271
12272
		/**
12273
		 * Handle user name update.
12274
		 *
12275
		 * @author Vova Feldman (@svovaf)
12276
		 * @since  1.0.9
12277
		 * @uses   FS_Api
12278
		 *
12279
		 * @return object
12280
		 */
12281
		private function update_user_name() {
12282
			$this->_logger->entrance();
12283
			$name = fs_request_get( 'fs_user_name_' . $this->get_unique_affix(), '' );
12284
12285
			$api  = $this->get_api_user_scope();
12286
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array(
12287
				'name' => $name,
12288
			) );
12289
12290
			if ( ! isset( $user->error ) ) {
12291
				$this->_user->first = $user->first;
12292
				$this->_user->last  = $user->last;
12293
				$this->_store_user();
12294
			} 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...
12295
				// handle different error cases.
12296
12297
			}
12298
12299
			return $user;
12300
		}
12301
12302
		/**
12303
		 * Verify user email.
12304
		 *
12305
		 * @author Vova Feldman (@svovaf)
12306
		 * @since  1.0.3
12307
		 * @uses   FS_Api
12308
		 */
12309
		private function verify_email() {
12310
			$this->_handle_account_user_sync();
12311
12312
			if ( $this->_user->is_verified() ) {
12313
				return;
12314
			}
12315
12316
			$api    = $this->get_api_site_scope();
12317
			$result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array(
12318
				'after_email_confirm_url' => $this->_get_admin_page_url(
12319
					'account',
12320
					array( 'fs_action' => 'sync_user' )
12321
				)
12322
			) );
12323
12324
			if ( ! isset( $result->error ) ) {
12325
				$this->_admin_notices->add( sprintf(
12326
					$this->get_text_inline( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.', 'verification-email-sent-message' ),
12327
					sprintf( '<a href="mailto:%1s">%2s</a>', esc_url( $this->_user->email ), $this->_user->email )
12328
				) );
12329
			} 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...
12330
				// handle different error cases.
12331
12332
			}
12333
		}
12334
12335
		/**
12336
		 * @author Vova Feldman (@svovaf)
12337
		 * @since  1.1.2
12338
		 *
12339
		 * @param array $params
12340
		 *
12341
		 * @return string
12342
		 */
12343
		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...
12344
			if ( $this->is_addon() && $this->has_free_plan() ) {
12345
				/**
12346
				 * @author Vova Feldman (@svovaf)
12347
				 * @since  1.2.1.7 Add-on's activation is the parent's module activation.
12348
				 */
12349
				return $this->get_parent_instance()->get_activation_url( $params );
12350
			}
12351
12352
			return $this->apply_filters( 'connect_url', $this->_get_admin_page_url( '', $params ) );
12353
		}
12354
12355
		/**
12356
		 * @author Vova Feldman (@svovaf)
12357
		 * @since  1.2.1.5
12358
		 *
12359
		 * @param array $params
12360
		 *
12361
		 * @return string
12362
		 */
12363
		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...
12364
			$params['fs_action']       = 'reset_anonymous_mode';
12365
			$params['fs_unique_affix'] = $this->get_unique_affix();
12366
12367
			return $this->get_activation_url( $params );
12368
		}
12369
12370
		/**
12371
		 * Get the URL of the page that should be loaded after the user connect
12372
		 * or skip in the opt-in screen.
12373
		 *
12374
		 * @author Vova Feldman (@svovaf)
12375
		 * @since  1.1.3
12376
		 *
12377
		 * @param string $filter Filter name.
12378
		 * @param array  $params Since 1.2.2.7
12379
		 *
12380
		 * @return string
12381
		 */
12382
		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...
12383
			if ( $this->is_free_wp_org_theme() &&
12384
			     fs_request_has( 'pending_activation' )
12385
			) {
12386
				$first_time_path = '';
12387
			} else {
12388
				$first_time_path = $this->_menu->get_first_time_path();
12389
			}
12390
12391
			return add_query_arg( $params, $this->apply_filters(
12392
				$filter,
12393
				empty( $first_time_path ) ?
12394
					$this->_get_admin_page_url() :
12395
					$first_time_path
12396
			) );
12397
		}
12398
12399
		/**
12400
		 * Handle account page updates / edits / actions.
12401
		 *
12402
		 * @author Vova Feldman (@svovaf)
12403
		 * @since  1.0.2
12404
		 *
12405
		 */
12406
		private function _handle_account_edits() {
12407
			if ( ! $this->is_user_admin() ) {
12408
				return;
12409
			}
12410
12411
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
12412
			$action    = fs_get_action();
12413
12414
			// Alias.
12415
			$oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...';
12416
12417
			switch ( $action ) {
12418
				case 'delete_account':
12419
					check_admin_referer( $action );
12420
12421
					if ( $plugin_id == $this->get_id() ) {
12422
						$this->delete_account_event();
12423
12424
						// Clear user and site.
12425
						$this->_site = null;
12426
						$this->_user = null;
12427
12428
						fs_redirect( $this->get_activation_url() );
12429
					} else {
12430
						if ( $this->is_addon_activated( $plugin_id ) ) {
12431
							$fs_addon = self::get_instance_by_id( $plugin_id );
12432
							$fs_addon->delete_account_event();
12433
12434
							fs_redirect( $this->_get_admin_page_url( 'account' ) );
12435
						}
12436
					}
12437
12438
					return;
12439
12440
				case 'downgrade_account':
12441
					check_admin_referer( $action );
12442
12443
					if ( $plugin_id == $this->get_id() ) {
12444
						$this->_downgrade_site();
12445
					} else if ( $this->is_addon_activated( $plugin_id ) ) {
12446
						$fs_addon = self::get_instance_by_id( $plugin_id );
12447
						$fs_addon->_downgrade_site();
12448
					}
12449
12450
					return;
12451
12452
				case 'activate_license':
12453
					check_admin_referer( $action );
12454
12455
					if ( $plugin_id == $this->get_id() ) {
12456
						$this->_activate_license();
12457
					} else {
12458
						if ( $this->is_addon_activated( $plugin_id ) ) {
12459
							$fs_addon = self::get_instance_by_id( $plugin_id );
12460
							$fs_addon->_activate_license();
12461
						}
12462
					}
12463
12464
					return;
12465
12466
				case 'deactivate_license':
12467
					check_admin_referer( $action );
12468
12469
					if ( $plugin_id == $this->get_id() ) {
12470
						$this->_deactivate_license();
12471
12472
                        if ( $this->is_only_premium() ) {
12473
                            // Clear user and site.
12474
                            $this->_site = null;
12475
                            $this->_user = null;
12476
12477
                            fs_redirect( $this->get_activation_url() );
12478
                        }
12479
					} else {
12480
						if ( $this->is_addon_activated( $plugin_id ) ) {
12481
							$fs_addon = self::get_instance_by_id( $plugin_id );
12482
							$fs_addon->_deactivate_license();
12483
						}
12484
					}
12485
12486
					return;
12487
12488
				case 'check_updates':
12489
					check_admin_referer( $action );
12490
					$this->check_updates();
12491
12492
					return;
12493
12494
				case 'change_owner':
12495
					$state = fs_request_get( 'state', 'init' );
12496
					switch ( $state ) {
12497
						case 'init':
12498
							$candidate_email = fs_request_get( 'candidate_email', '' );
12499
12500
							if ( $this->init_change_owner( $candidate_email ) ) {
12501
								$this->_admin_notices->add( sprintf( $this->get_text_inline( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.', 'change-owner-request-sent-x' ), '<b>' . $this->_user->email . '</b>' ) );
12502
							}
12503
							break;
12504
						case 'owner_confirmed':
12505
							$candidate_email = fs_request_get( 'candidate_email', '' );
12506
12507
							$this->_admin_notices->add( sprintf( $this->get_text_inline( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.', 'change-owner-request_owner-confirmed' ), '<b>' . $candidate_email . '</b>' ) );
12508
							break;
12509
						case 'candidate_confirmed':
12510
							if ( $this->complete_change_owner() ) {
12511
								$this->_admin_notices->add_sticky(
12512
									sprintf( $this->get_text_inline( '%s is the new owner of the account.', 'change-owner-request_candidate-confirmed' ), '<b>' . $this->_user->email . '</b>' ),
12513
									'ownership_changed',
12514
									$this->get_text_x_inline( 'Congrats', 'as congratulations', 'congrats' ) . '!'
12515
								);
12516
							} 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...
12517
								// @todo Handle failed ownership change message.
12518
							}
12519
							break;
12520
					}
12521
12522
					return;
12523
12524
				case 'update_email':
12525
					check_admin_referer( 'update_email' );
12526
12527
					$new_email = fs_request_get( 'fs_email_' . $this->get_unique_affix(), '' );
12528
					$result    = $this->update_email( $new_email );
12529
12530
					if ( isset( $result->error ) ) {
12531
						switch ( $result->error->code ) {
12532
							case 'user_exist':
12533
								$this->_admin_notices->add(
12534
									$this->get_text_inline( 'Sorry, we could not complete the email update. Another user with the same email is already registered.', 'user-exist-message' ) . ' ' .
12535
									sprintf( $this->get_text_inline( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.', 'user-exist-message_ownership' ), $this->_module_type, '<b>' . $new_email . '</b>' ) .
12536
									sprintf(
12537
										'<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
12538
										$this->get_account_url( 'change_owner', array(
12539
											'state'           => 'init',
12540
											'candidate_email' => $new_email
12541
										) ),
12542
										$this->get_text_inline( 'Change Ownership', 'change-ownership' )
12543
									),
12544
									$oops_text,
12545
									'error'
12546
								);
12547
								break;
12548
						}
12549
					} else {
12550
						$this->_admin_notices->add( $this->get_text_inline( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.', 'email-updated-message' ) );
12551
					}
12552
12553
					return;
12554
12555
				case 'update_user_name':
12556
					check_admin_referer( 'update_user_name' );
12557
12558
					$result = $this->update_user_name();
12559
12560
					if ( isset( $result->error ) ) {
12561
						$this->_admin_notices->add(
12562
							$this->get_text_inline( 'Please provide your full name.', 'name-update-failed-message' ),
12563
							$oops_text,
12564
							'error'
12565
						);
12566
					} else {
12567
						$this->_admin_notices->add( $this->get_text_inline( 'Your name was successfully updated.', 'name-updated-message' ) );
12568
					}
12569
12570
					return;
12571
12572
				#region Actions that might be called from external links (e.g. email)
12573
12574
				case 'cancel_trial':
12575
					if ( $plugin_id == $this->get_id() ) {
12576
						$this->_cancel_trial();
12577
					} else {
12578
						if ( $this->is_addon_activated( $plugin_id ) ) {
12579
							$fs_addon = self::get_instance_by_id( $plugin_id );
12580
							$fs_addon->_cancel_trial();
12581
						}
12582
					}
12583
12584
					return;
12585
12586
				case 'verify_email':
12587
					$this->verify_email();
12588
12589
					return;
12590
12591
				case 'sync_user':
12592
					$this->_handle_account_user_sync();
12593
12594
					return;
12595
12596
				case $this->get_unique_affix() . '_sync_license':
12597
					$this->_sync_license();
12598
12599
					return;
12600
12601
				case 'download_latest':
12602
					$this->download_latest_directly( $plugin_id );
12603
12604
					return;
12605
12606
				#endregion
12607
			}
12608
12609
			if ( WP_FS__IS_POST_REQUEST ) {
12610
				$properties = array( 'site_secret_key', 'site_id', 'site_public_key' );
12611
				foreach ( $properties as $p ) {
12612
					if ( 'update_' . $p === $action ) {
12613
						check_admin_referer( $action );
12614
12615
						$this->_logger->log( $action );
12616
12617
						$site_property                      = substr( $p, strlen( 'site_' ) );
12618
						$site_property_value                = fs_request_get( 'fs_' . $p . '_' . $this->get_unique_affix(), '' );
12619
						$this->get_site()->{$site_property} = $site_property_value;
12620
12621
						// Store account after modification.
12622
						$this->_store_site();
12623
12624
						$this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value );
12625
12626
						$this->_admin_notices->add( sprintf(
12627
							/* translators: %s: User's account property (e.g. email address, name) */
12628
							$this->get_text_inline( 'You have successfully updated your %s.', 'x-updated' ),
12629
							'<b>' . str_replace( '_', ' ', $p ) . '</b>'
12630
						) );
12631
12632
						return;
12633
					}
12634
				}
12635
			}
12636
		}
12637
12638
		/**
12639
		 * Account page resources load.
12640
		 *
12641
		 * @author Vova Feldman (@svovaf)
12642
		 * @since  1.0.6
12643
		 */
12644
		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...
12645
			$this->_logger->entrance();
12646
12647
			$this->_logger->info( var_export( $_REQUEST, true ) );
12648
12649
			fs_enqueue_local_style( 'fs_account', '/admin/account.css' );
12650
12651
			if ( $this->has_addons() ) {
12652
				wp_enqueue_script( 'plugin-install' );
12653
				add_thickbox();
12654
12655
				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...
12656
					$classes .= ' plugins-php';
12657
12658
					return $classes;
12659
				}
12660
12661
				add_filter( 'admin_body_class', 'fs_addons_body_class' );
12662
			}
12663
12664
			if ( $this->has_paid_plan() &&
12665
			     ! $this->has_any_license() &&
12666
			     ! $this->is_sync_executed() &&
12667
			     $this->is_tracking_allowed()
12668
			) {
12669
				/**
12670
				 * If no licenses found and no sync job was executed during the last 24 hours,
12671
				 * just execute the sync job right away (blocking execution).
12672
				 *
12673
				 * @since 1.1.7.3
12674
				 */
12675
				$this->run_manual_sync();
12676
			}
12677
12678
			$this->_handle_account_edits();
12679
12680
			$this->do_action( 'account_page_load_before_departure' );
12681
		}
12682
12683
		/**
12684
		 * Renders the "Affiliation" page.
12685
		 *
12686
		 * @author Leo Fajardo (@leorw)
12687
		 * @since  1.2.3
12688
		 */
12689
		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...
12690
			$this->_logger->entrance();
12691
12692
            $this->fetch_affiliate_and_terms();
12693
12694
            fs_enqueue_local_style( 'fs_affiliation', '/admin/affiliation.css' );
12695
12696
            $vars = array( 'id' => $this->_module_id );
12697
			echo $this->apply_filters( "/forms/affiliation.php", fs_get_template( '/forms/affiliation.php', $vars ) );
12698
		}
12699
12700
12701
		/**
12702
		 * Render account page.
12703
		 *
12704
		 * @author Vova Feldman (@svovaf)
12705
		 * @since  1.0.0
12706
		 */
12707
		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...
12708
			$this->_logger->entrance();
12709
12710
			$template = 'account.php';
12711
			$vars     = array( 'id' => $this->_module_id );
12712
12713
			/**
12714
			 * Added filter to the template to allow developers wrapping the template
12715
			 * in custom HTML (e.g. within a wizard/tabs).
12716
			 *
12717
			 * @author Vova Feldman (@svovaf)
12718
			 * @since  1.2.1.6
12719
			 */
12720
			echo $this->apply_filters( "templates/{$template}", fs_get_template( $template, $vars ) );
12721
		}
12722
12723
		/**
12724
		 * Render account connect page.
12725
		 *
12726
		 * @author Vova Feldman (@svovaf)
12727
		 * @since  1.0.7
12728
		 */
12729
		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...
12730
			$this->_logger->entrance();
12731
12732
			$vars = array( 'id' => $this->_module_id );
12733
12734
			/**
12735
			 * Added filter to the template to allow developers wrapping the template
12736
			 * in custom HTML (e.g. within a wizard/tabs).
12737
			 *
12738
			 * @author Vova Feldman (@svovaf)
12739
			 * @since  1.2.1.6
12740
			 */
12741
			echo $this->apply_filters( 'templates/connect.php', fs_get_template( 'connect.php', $vars ) );
12742
		}
12743
12744
		/**
12745
		 * Load required resources before add-ons page render.
12746
		 *
12747
		 * @author Vova Feldman (@svovaf)
12748
		 * @since  1.0.6
12749
		 */
12750
		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...
12751
			$this->_logger->entrance();
12752
12753
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
12754
12755
			wp_enqueue_script( 'plugin-install' );
12756
			add_thickbox();
12757
12758
			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 (L12655-12659) 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...
12759
				$classes .= ' plugins-php';
12760
12761
				return $classes;
12762
			}
12763
12764
			add_filter( 'admin_body_class', 'fs_addons_body_class' );
12765
12766
			if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) {
12767
				$this->_admin_notices->add(
12768
					sprintf( $this->get_text_inline( 'Just letting you know that the add-ons information of %s is being pulled from an external server.', 'addons-info-external-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
12769
					$this->get_text_x_inline( 'Heads up', 'advance notice of something that will need attention.', 'heads-up' ),
12770
					'update-nag'
12771
				);
12772
			}
12773
		}
12774
12775
		/**
12776
		 * Render add-ons page.
12777
		 *
12778
		 * @author Vova Feldman (@svovaf)
12779
		 * @since  1.0.6
12780
		 */
12781
		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...
12782
			$this->_logger->entrance();
12783
12784
			$vars = array( 'id' => $this->_module_id );
12785
12786
			/**
12787
			 * Added filter to the template to allow developers wrapping the template
12788
			 * in custom HTML (e.g. within a wizard/tabs).
12789
			 *
12790
			 * @author Vova Feldman (@svovaf)
12791
			 * @since  1.2.1.6
12792
			 */
12793
			echo $this->apply_filters( 'templates/add-ons.php', fs_get_template( 'add-ons.php', $vars ) );
12794
		}
12795
12796
		/* Pricing & Upgrade
12797
		------------------------------------------------------------------------------------------------------------------*/
12798
		/**
12799
		 * Render pricing page.
12800
		 *
12801
		 * @author Vova Feldman (@svovaf)
12802
		 * @since  1.0.0
12803
		 */
12804
		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...
12805
			$this->_logger->entrance();
12806
12807
			$vars = array( 'id' => $this->_module_id );
12808
12809
			if ( 'true' === fs_request_get( 'checkout', false ) ) {
12810
				fs_require_once_template( 'checkout.php', $vars );
12811
			} else {
12812
				fs_require_once_template( 'pricing.php', $vars );
12813
			}
12814
		}
12815
12816
		#----------------------------------------------------------------------------------
12817
		#region Contact Us
12818
		#----------------------------------------------------------------------------------
12819
12820
		/**
12821
		 * Render contact-us page.
12822
		 *
12823
		 * @author Vova Feldman (@svovaf)
12824
		 * @since  1.0.3
12825
		 */
12826
		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...
12827
			$this->_logger->entrance();
12828
12829
			$vars = array( 'id' => $this->_module_id );
12830
			fs_require_once_template( 'contact.php', $vars );
12831
		}
12832
12833
		#endregion ------------------------------------------------------------------------
12834
12835
		/**
12836
		 * Hide all admin notices to prevent distractions.
12837
		 *
12838
		 * @author Vova Feldman (@svovaf)
12839
		 * @since  1.0.3
12840
		 *
12841
		 * @uses   remove_all_actions()
12842
		 */
12843
		private static function _hide_admin_notices() {
12844
			remove_all_actions( 'admin_notices' );
12845
			remove_all_actions( 'network_admin_notices' );
12846
			remove_all_actions( 'all_admin_notices' );
12847
			remove_all_actions( 'user_admin_notices' );
12848
		}
12849
12850
		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...
12851
			self::_hide_admin_notices();
12852
12853
			// Hide footer.
12854
			echo '<style>#wpfooter { display: none !important; }</style>';
12855
		}
12856
12857
		/**
12858
		 * Attach to admin_head hook to hide all admin notices.
12859
		 *
12860
		 * @author Vova Feldman (@svovaf)
12861
		 * @since  1.0.3
12862
		 */
12863
		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...
12864
			add_action( 'admin_head', 'Freemius::_clean_admin_content_section_hook' );
12865
		}
12866
12867
		/* CSS & JavaScript
12868
		------------------------------------------------------------------------------------------------------------------*/
12869
		/*		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...
12870
					$url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src );
12871
12872
					$this->_logger->entrance( 'script = ' . $url );
12873
12874
					wp_enqueue_script( $handle, $url );
12875
				}*/
12876
12877
		/* SDK
12878
		------------------------------------------------------------------------------------------------------------------*/
12879
		private $_user_api;
12880
12881
		/**
12882
		 *
12883
		 * @author Vova Feldman (@svovaf)
12884
		 * @since  1.0.2
12885
		 *
12886
		 * @param bool $flush
12887
		 *
12888
		 * @return FS_Api
12889
		 */
12890
		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...
12891
			if ( ! isset( $this->_user_api ) || $flush ) {
12892
				$this->_user_api = FS_Api::instance(
12893
					$this->_module_id,
12894
					'user',
12895
					$this->_user->id,
12896
					$this->_user->public_key,
12897
					! $this->is_live(),
12898
					$this->_user->secret_key
12899
				);
12900
			}
12901
12902
			return $this->_user_api;
12903
		}
12904
12905
		private $_site_api;
12906
12907
		/**
12908
		 *
12909
		 * @author Vova Feldman (@svovaf)
12910
		 * @since  1.0.2
12911
		 *
12912
		 * @param bool $flush
12913
		 *
12914
		 * @return FS_Api
12915
		 */
12916
		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...
12917
			if ( ! isset( $this->_site_api ) || $flush ) {
12918
				$this->_site_api = FS_Api::instance(
12919
					$this->_module_id,
12920
					'install',
12921
					$this->_site->id,
12922
					$this->_site->public_key,
12923
					! $this->is_live(),
12924
					$this->_site->secret_key
12925
				);
12926
			}
12927
12928
			return $this->_site_api;
12929
		}
12930
12931
		private $_plugin_api;
12932
12933
		/**
12934
		 * Get plugin public API scope.
12935
		 *
12936
		 * @author Vova Feldman (@svovaf)
12937
		 * @since  1.0.7
12938
		 *
12939
		 * @return FS_Api
12940
		 */
12941
		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...
12942
			if ( ! isset( $this->_plugin_api ) ) {
12943
				$this->_plugin_api = FS_Api::instance(
12944
					$this->_module_id,
12945
					'plugin',
12946
					$this->_plugin->id,
12947
					$this->_plugin->public_key,
12948
					! $this->is_live()
12949
				);
12950
			}
12951
12952
			return $this->_plugin_api;
12953
		}
12954
12955
		/**
12956
		 * Get site API scope object (fallback to public plugin scope when not registered).
12957
		 *
12958
		 * @author Vova Feldman (@svovaf)
12959
		 * @since  1.0.7
12960
		 *
12961
		 * @return FS_Api
12962
		 */
12963
		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...
12964
			return $this->is_registered() ?
12965
				$this->get_api_site_scope() :
12966
				$this->get_api_plugin_scope();
12967
		}
12968
12969
		/**
12970
		 * Show trial promotional notice (if any trial exist).
12971
		 *
12972
		 * @author Vova Feldman (@svovaf)
12973
		 * @since  1.0.9
12974
		 *
12975
		 * @param $plans
12976
		 */
12977
		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...
12978
			$this->_storage->has_trial_plan = FS_Plan_Manager::instance()->has_trial_plan( $plans );
12979
		}
12980
12981
		/**
12982
		 * During trial promotion the "upgrade" submenu item turns to
12983
		 * "start trial" to encourage the trial. Since we want to keep
12984
		 * the same menu item handler and there's no robust way to
12985
		 * add new arguments to the menu item link's querystring,
12986
		 * use JavaScript to find the menu item and update the href of
12987
		 * the link.
12988
		 *
12989
		 * @author Vova Feldman (@svovaf)
12990
		 * @since  1.2.1.5
12991
		 */
12992
		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...
12993
			$template_args = array( 'id' => $this->_module_id );
12994
			fs_require_template( 'add-trial-to-pricing.php', $template_args );
12995
		}
12996
12997
		/**
12998
		 * Check if module is currently in a trial promotion mode.
12999
		 *
13000
		 * @author Vova Feldman (@svovaf)
13001
		 * @since  1.2.2.7
13002
		 *
13003
		 * @return bool
13004
		 */
13005
		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...
13006
			return $this->_admin_notices->has_sticky( 'trial_promotion' );
13007
		}
13008
13009
		/**
13010
		 * Show trial promotional notice (if any trial exist).
13011
		 *
13012
		 * @author Vova Feldman (@svovaf)
13013
		 * @since  1.0.9
13014
		 *
13015
		 * @return bool If trial notice added.
13016
		 */
13017
		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...
13018
			if ( ! $this->is_user_admin() ) {
13019
				return false;
13020
			}
13021
13022
			if ( ! $this->is_user_in_admin() ) {
13023
				return false;
13024
			}
13025
13026
			// Check if trial message is already shown.
13027
			if ( $this->is_in_trial_promotion() ) {
13028
				add_action( 'admin_footer', array( &$this, '_fix_start_trial_menu_item_url' ) );
13029
13030
				$this->_menu->add_counter_to_menu_item( 1, 'fs-trial' );
13031
13032
				return false;
13033
			}
13034
13035
			if ( $this->is_premium() && ! WP_FS__DEV_MODE ) {
13036
				// Don't show trial if running the premium code, unless running in DEV mode.
13037
				return false;
13038
			}
13039
13040
			if ( ! $this->has_trial_plan() ) {
13041
				// No plans with trial.
13042
				return false;
13043
			}
13044
13045
			if ( ! $this->apply_filters( 'show_trial', true ) ) {
13046
				// Developer explicitly asked not to show the trial promo.
13047
				return false;
13048
			}
13049
13050
			if ( $this->is_registered() ) {
13051
				// Check if trial already utilized.
13052
				if ( $this->_site->is_trial_utilized() ) {
13053
					return false;
13054
				}
13055
13056
				if ( $this->is_paying_or_trial() ) {
13057
					// Don't show trial if paying or already in trial.
13058
					return false;
13059
				}
13060
			}
13061
13062
			if ( $this->is_activation_mode() || $this->is_pending_activation() ) {
13063
				// If not yet opted-in/skipped, or pending activation, don't show trial.
13064
				return false;
13065
			}
13066
13067
			$last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false );
13068
			$was_promotion_shown_before      = ( false !== $last_time_trial_promotion_shown );
13069
13070
			// Show promotion if never shown before and 24 hours after initial activation with FS.
13071
			if ( ! $was_promotion_shown_before &&
13072
			     $this->_storage->install_timestamp > ( time() - WP_FS__TIME_24_HOURS_IN_SEC )
13073
			) {
13074
				return false;
13075
			}
13076
13077
			// OR if promotion was shown before, try showing it every 30 days.
13078
			if ( $was_promotion_shown_before &&
13079
			     30 * WP_FS__TIME_24_HOURS_IN_SEC > time() - $last_time_trial_promotion_shown
13080
			) {
13081
				return false;
13082
			}
13083
13084
			$trial_period    = $this->_trial_days;
13085
			$require_payment = $this->_is_trial_require_payment;
13086
			$trial_url       = $this->get_trial_url();
13087
			$plans_string    = strtolower( $this->get_text_inline( 'Awesome', 'awesome' ) );
13088
13089
			if ( $this->is_registered() ) {
13090
				// If opted-in, override trial with up to date data from API.
13091
				$trial_plans       = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
13092
				$trial_plans_count = count( $trial_plans );
13093
13094
				if ( 0 === $trial_plans_count ) {
13095
					// If there's no plans with a trial just exit.
13096
					return false;
13097
				}
13098
13099
				/**
13100
				 * @var FS_Plugin_Plan $paid_plan
13101
				 */
13102
				$paid_plan       = $trial_plans[0];
13103
				$require_payment = $paid_plan->is_require_subscription;
13104
				$trial_period    = $paid_plan->trial_period;
13105
13106
				$total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 );
13107
13108
				if ( $total_paid_plans !== $trial_plans_count ) {
13109
					// Not all paid plans have a trial - generate a string of those that have it.
13110
					for ( $i = 0; $i < $trial_plans_count; $i ++ ) {
13111
						$plans_string .= sprintf(
13112
							' <a href="%s">%s</a>',
13113
							$trial_url,
13114
							$trial_plans[ $i ]->title
13115
						);
13116
13117
						if ( $i < $trial_plans_count - 2 ) {
13118
							$plans_string .= ', ';
13119
						} else if ( $i == $trial_plans_count - 2 ) {
13120
							$plans_string .= ' and ';
13121
						}
13122
					}
13123
				}
13124
			}
13125
13126
			$message = sprintf(
13127
				$this->get_text_x_inline( 'Hey', 'exclamation', 'hey' ) . '! ' . $this->get_text_inline( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.', 'trial-x-promotion-message' ),
13128
				sprintf( '<b>%s</b>', $this->get_plugin_name() ),
13129
				$plans_string,
13130
				$trial_period
13131
			);
13132
13133
			// "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...
13134
			$cc_string = $require_payment ?
13135
				sprintf( $this->get_text_inline( 'No commitment for %s days - cancel anytime!', 'no-commitment-for-x-days' ), $trial_period ) :
13136
				$this->get_text_inline( 'No credit card required', 'no-cc-required' ) . '!';
13137
13138
13139
			// Start trial button.
13140
			$button = ' ' . sprintf(
13141
					'<a style="margin-left: 10px; vertical-align: super;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
13142
					$trial_url,
13143
					$this->get_text_x_inline( 'Start free trial', 'call to action', 'start-free-trial' )
13144
				);
13145
13146
			$this->_admin_notices->add_sticky(
13147
				$this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ),
13148
				'trial_promotion',
13149
				'',
13150
				'promotion'
13151
			);
13152
13153
			$this->_storage->trial_promotion_shown = WP_FS__SCRIPT_START_TIME;
13154
13155
			return true;
13156
		}
13157
13158
		/**
13159
		 * Lets users/customers know that the product has an affiliate program.
13160
		 *
13161
		 * @author Leo Fajardo (@leorw)
13162
		 * @since  1.2.2.11
13163
		 *
13164
		 * @return bool Returns true if the notice has been added.
13165
		 */
13166
		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...
13167
			if ( ! $this->is_user_admin() ) {
13168
				return false;
13169
			}
13170
13171
			if ( ! $this->is_user_in_admin() ) {
13172
				return false;
13173
			}
13174
13175
			// Check if the notice is already shown.
13176
			if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) {
13177
				return false;
13178
			}
13179
13180
            if (
13181
                // Product has no affiliate program.
13182
                ! $this->has_affiliate_program() ||
13183
                // User has applied for an affiliate account.
13184
                ! 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...
13185
                return false;
13186
            }
13187
13188
            if ( ! $this->apply_filters( 'show_affiliate_program_notice', true ) ) {
13189
                // Developer explicitly asked not to show the notice about the affiliate program.
13190
                return false;
13191
            }
13192
13193
            if ( $this->is_activation_mode() || $this->is_pending_activation() ) {
13194
                // If not yet opted in/skipped, or pending activation, don't show the notice.
13195
                return false;
13196
            }
13197
13198
            $last_time_notice_was_shown = $this->_storage->get( 'affiliate_program_notice_shown', false );
13199
            $was_notice_shown_before    = ( false !== $last_time_notice_was_shown );
13200
13201
            /**
13202
             * Do not show the notice if it was already shown before or less than 30 days have passed since the initial
13203
             * activation with FS.
13204
             */
13205
            if ( $was_notice_shown_before ||
13206
                $this->_storage->install_timestamp > ( time() - ( WP_FS__TIME_24_HOURS_IN_SEC * 30 ) )
13207
            ) {
13208
                return false;
13209
            }
13210
13211
			if ( ! $this->is_paying() &&
13212
                FS_Plugin::AFFILIATE_MODERATION_CUSTOMERS == $this->_plugin->affiliate_moderation ) {
13213
			    // If the user is not a customer and the affiliate program is only for customers, don't show the notice.
13214
                return false;
13215
			}
13216
13217
			$message = sprintf(
13218
				$this->get_text_inline( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!', 'become-an-ambassador-admin-notice' ),
13219
				sprintf( '<strong>%s</strong>', $this->get_plugin_name() ),
13220
				$this->get_module_label( true )
13221
			);
13222
13223
			// HTML code for the "Learn more..." button.
13224
			$button = ' ' . sprintf(
13225
					'<a style="display: block; margin-top: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
13226
                    $this->_get_admin_page_url( 'affiliation' ),
13227
					$this->get_text_inline( 'Learn more', 'learn-more' ) . '...'
13228
				);
13229
13230
			$this->_admin_notices->add_sticky(
13231
				$this->apply_filters( 'affiliate_program_notice', "{$message} {$button}" ),
13232
				'affiliate_program',
13233
				'',
13234
				'promotion'
13235
			);
13236
13237
			$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...
13238
13239
			return true;
13240
		}
13241
13242
		/**
13243
		 * @author Vova Feldman (@svovaf)
13244
		 * @since  1.2.1.5
13245
		 */
13246
		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...
13247
			if ( $this->has_paid_plan() && ! $this->is_paying() ) {
13248
				// Add basic CSS for admin-notices and menu-item colors.
13249
				fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
13250
			}
13251
		}
13252
13253
		/**
13254
		 * @author Leo Fajardo (leorw)
13255
		 * @since  1.2.2
13256
		 */
13257
		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...
13258
			fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' );
13259
13260
			add_action( 'admin_footer-themes.php', array( &$this, '_add_fs_theme_activation_dialog' ) );
13261
		}
13262
13263
		/**
13264
		 * @author Leo Fajardo (leorw)
13265
		 * @since  1.2.2
13266
		 */
13267
		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...
13268
			$vars = array( 'id' => $this->_module_id );
13269
			fs_require_once_template( 'connect.php', $vars );
13270
		}
13271
13272
		/* Action Links
13273
		------------------------------------------------------------------------------------------------------------------*/
13274
		private $_action_links_hooked = false;
13275
		private $_action_links = array();
13276
13277
		/**
13278
		 * Hook to plugin action links filter.
13279
		 *
13280
		 * @author Vova Feldman (@svovaf)
13281
		 * @since  1.0.0
13282
		 */
13283
		private function hook_plugin_action_links() {
13284
			$this->_logger->entrance();
13285
13286
			$this->_action_links_hooked = true;
13287
13288
			$this->_logger->log( 'Adding action links hooks.' );
13289
13290
			// Add action link to settings page.
13291
			add_filter( 'plugin_action_links_' . $this->_plugin_basename, array(
13292
				&$this,
13293
				'_modify_plugin_action_links_hook'
13294
			), WP_FS__DEFAULT_PRIORITY, 2 );
13295
			add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array(
13296
				&$this,
13297
				'_modify_plugin_action_links_hook'
13298
			), WP_FS__DEFAULT_PRIORITY, 2 );
13299
		}
13300
13301
		/**
13302
		 * Add plugin action link.
13303
		 *
13304
		 * @author Vova Feldman (@svovaf)
13305
		 * @since  1.0.0
13306
		 *
13307
		 * @param      $label
13308
		 * @param      $url
13309
		 * @param bool $external
13310
		 * @param int  $priority
13311
		 * @param bool $key
13312
		 */
13313
		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...
13314
			$this->_logger->entrance();
13315
13316
			if ( ! isset( $this->_action_links[ $priority ] ) ) {
13317
				$this->_action_links[ $priority ] = array();
13318
			}
13319
13320
			if ( false === $key ) {
13321
				$key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) );
13322
			}
13323
13324
			$this->_action_links[ $priority ][] = array(
13325
				'label'    => $label,
13326
				'href'     => $url,
13327
				'key'      => $key,
13328
				'external' => $external
13329
			);
13330
		}
13331
13332
		/**
13333
		 * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection.
13334
		 *
13335
		 * @author Vova Feldman (@svovaf)
13336
		 * @since  1.0.0
13337
		 */
13338
		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...
13339
			$this->_logger->entrance();
13340
13341
			if ( $this->is_registered() ) {
13342
				if ( ! $this->is_paying() && $this->has_paid_plan() ) {
13343
					$this->add_plugin_action_link(
13344
						$this->get_text_inline( 'Upgrade', 'upgrade' ),
13345
						$this->get_upgrade_url(),
13346
						false,
13347
						7,
13348
						'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...
13349
					);
13350
				}
13351
13352
				if ( $this->has_addons() ) {
13353
					$this->add_plugin_action_link(
13354
						$this->get_text_inline( 'Add-Ons', 'add-ons' ),
13355
						$this->_get_admin_page_url( 'addons' ),
13356
						false,
13357
						9,
13358
						'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...
13359
					);
13360
				}
13361
			}
13362
		}
13363
13364
		/**
13365
		 * Adds "Activate License" or "Change License" link to the main Plugins page link actions collection.
13366
		 *
13367
		 * @author Leo Fajardo (@leorw)
13368
		 * @since  1.1.9
13369
		 */
13370
		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...
13371
			$this->_logger->entrance();
13372
13373
			if ( $this->is_free_plan() && $this->is_addon() ) {
13374
				return;
13375
			}
13376
13377
			if ( ! self::is_ajax() ) {
13378
				// Inject license activation dialog UI and client side code.
13379
				add_action( 'admin_footer', array( &$this, '_add_license_activation_dialog_box' ) );
13380
			}
13381
13382
			$link_text = $this->is_free_plan() ?
13383
				$this->get_text_inline( 'Activate License', 'activate-license' ) :
13384
				$this->get_text_inline( 'Change License', 'change-license' );
13385
13386
			$this->add_plugin_action_link(
13387
				$link_text,
13388
				'#',
13389
				false,
13390
				11,
13391
				( '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...
13392
			);
13393
		}
13394
13395
		/**
13396
		 * Adds "Opt in" or "Opt out" link to the main "Plugins" page link actions collection.
13397
		 *
13398
		 * @author Leo Fajardo (@leorw)
13399
		 * @since  1.2.1.5
13400
		 */
13401
		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...
13402
			if ( ! current_user_can( 'activate_plugins' ) ) {
13403
				return;
13404
			}
13405
13406
			$this->_logger->entrance();
13407
13408
			if ( fs_request_is_action_secure( $this->get_unique_affix() . '_reconnect' ) ) {
13409
				if ( ! $this->is_registered() && $this->is_anonymous() ) {
13410
					$this->connect_again();
13411
13412
					return;
13413
				}
13414
			}
13415
13416
			if ( ( $this->is_plugin() && ! self::is_plugins_page() ) ||
13417
			     ( $this->is_theme() && ! self::is_themes_page() )
13418
			) {
13419
				// Only show tracking links on the plugins and themes pages.
13420
				return;
13421
			}
13422
13423
			if ( ! $this->is_enable_anonymous() ) {
13424
				// Don't allow to opt-out if anonymous mode is disabled.
13425
				return;
13426
			}
13427
13428
			if ( ! $this->is_free_plan() ) {
13429
				// Don't allow to opt-out if running in paid plan.
13430
				return;
13431
			}
13432
13433
			if ( $this->add_ajax_action( 'stop_tracking', array( &$this, '_stop_tracking_callback' ) ) ) {
13434
				return;
13435
			}
13436
13437
			if ( $this->add_ajax_action( 'allow_tracking', array( &$this, '_allow_tracking_callback' ) ) ) {
13438
				return;
13439
			}
13440
13441
			$url = '#';
13442
13443
			if ( $this->is_registered() ) {
13444
				if ( $this->is_tracking_allowed() ) {
13445
					$link_text_id = $this->get_text_inline( 'Opt Out', 'opt-out' );
13446
				} else {
13447
					$link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' );
13448
				}
13449
13450
				add_action( 'admin_footer', array( &$this, '_add_optout_dialog' ) );
13451
			} else {
13452
				$link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' );
13453
13454
				$params = ! $this->is_anonymous() ?
13455
					array() :
13456
					array(
13457
						'nonce'     => wp_create_nonce( $this->get_unique_affix() . '_reconnect' ),
13458
						'fs_action' => ( $this->get_unique_affix() . '_reconnect' ),
13459
					);
13460
13461
				$url = $this->get_activation_url( $params );
13462
			}
13463
13464
			if ( $this->is_plugin() && self::is_plugins_page() ) {
13465
				$this->add_plugin_action_link(
13466
                    $link_text_id,
13467
					$url,
13468
					false,
13469
					13,
13470
					"opt-in-or-opt-out {$this->_slug}"
13471
				);
13472
			}
13473
		}
13474
13475
		/**
13476
		 * Get the URL of the page that should be loaded right after the plugin activation.
13477
		 *
13478
		 * @author Vova Feldman (@svovaf)
13479
		 * @since  1.1.7.4
13480
		 *
13481
		 * @return string
13482
		 */
13483
		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...
13484
			$url       = false;
13485
13486
			if ( ! $this->is_addon() || ! $this->has_free_plan() ) {
13487
				$first_time_path = $this->_menu->get_first_time_path();
13488
				$url             = $this->is_activation_mode() ?
13489
					$this->get_activation_url() :
13490
					( empty( $first_time_path ) ?
13491
						$this->_get_admin_page_url() :
13492
						$first_time_path );
13493
			} else {
13494
				$plugin_fs = false;
13495
13496
				if ( $this->is_parent_plugin_installed() ) {
13497
					$plugin_fs = self::get_parent_instance();
13498
				}
13499
13500
				if ( is_object( $plugin_fs ) ) {
13501
					if ( ! $plugin_fs->is_registered() ) {
13502
						// Forward to parent plugin connect when parent not registered.
13503
						$url = $plugin_fs->get_activation_url();
13504
					} else {
13505
						// Forward to account page.
13506
						$url = $plugin_fs->_get_admin_page_url( 'account' );
13507
					}
13508
				}
13509
			}
13510
13511
			return $url;
13512
		}
13513
13514
		/**
13515
		 * Forward page to activation page.
13516
		 *
13517
		 * @author Vova Feldman (@svovaf)
13518
		 * @since  1.0.3
13519
		 */
13520
		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...
13521
			$url = $this->get_after_plugin_activation_redirect_url();
13522
13523
			if ( is_string( $url ) ) {
13524
				fs_redirect( $url );
13525
			}
13526
		}
13527
13528
		/**
13529
		 * Modify plugin's page action links collection.
13530
		 *
13531
		 * @author Vova Feldman (@svovaf)
13532
		 * @since  1.0.0
13533
		 *
13534
		 * @param array $links
13535
		 * @param       $file
13536
		 *
13537
		 * @return array
13538
		 */
13539
		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...
13540
			$this->_logger->entrance();
13541
13542
			$passed_deactivate = false;
13543
			$deactivate_link   = '';
13544
			$before_deactivate = array();
13545
			$after_deactivate  = array();
13546
			foreach ( $links as $key => $link ) {
13547
				if ( 'deactivate' === $key ) {
13548
					$deactivate_link   = $link;
13549
					$passed_deactivate = true;
13550
					continue;
13551
				}
13552
13553
				if ( ! $passed_deactivate ) {
13554
					$before_deactivate[ $key ] = $link;
13555
				} else {
13556
					$after_deactivate[ $key ] = $link;
13557
				}
13558
			}
13559
13560
			ksort( $this->_action_links );
13561
13562
			foreach ( $this->_action_links as $new_links ) {
13563
				foreach ( $new_links as $link ) {
13564
					$before_deactivate[ $link['key'] ] = '<a href="' . $link['href'] . '"' . ( $link['external'] ? ' target="_blank"' : '' ) . '>' . $link['label'] . '</a>';
13565
				}
13566
			}
13567
13568
			if ( ! empty( $deactivate_link ) ) {
13569
				/**
13570
				 * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link.
13571
				 *
13572
				 * @since 1.2.1.6 Always show the deactivation feedback form since we added automatic free version deactivation upon premium code activation.
13573
				 */
13574
				$deactivate_link .= '<i class="fs-module-id" data-module-id="' . $this->_module_id . '"></i>';
13575
13576
				// Append deactivation link.
13577
				$before_deactivate['deactivate'] = $deactivate_link;
13578
			}
13579
13580
			return array_merge( $before_deactivate, $after_deactivate );
13581
		}
13582
13583
		/**
13584
		 * Adds admin message.
13585
		 *
13586
		 * @author Vova Feldman (@svovaf)
13587
		 * @since  1.0.4
13588
		 *
13589
		 * @param string $message
13590
		 * @param string $title
13591
		 * @param string $type
13592
		 */
13593
		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...
13594
			$this->_admin_notices->add( $message, $title, $type );
13595
		}
13596
13597
		/**
13598
		 * Adds sticky admin message.
13599
		 *
13600
		 * @author Vova Feldman (@svovaf)
13601
		 * @since  1.1.0
13602
		 *
13603
		 * @param string $message
13604
		 * @param string $id
13605
		 * @param string $title
13606
		 * @param string $type
13607
		 */
13608
		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...
13609
			$this->_admin_notices->add_sticky( $message, $id, $title, $type );
13610
		}
13611
13612
		/**
13613
		 * Helper function that returns the final steps for the upgrade completion.
13614
		 *
13615
		 * If the module is already running the premium code, returns an empty string.
13616
		 *
13617
		 * @author Vova Feldman (@svovaf)
13618
		 * @since  1.2.1
13619
		 *
13620
		 * @param string $plan_title
13621
		 *
13622
		 * @return string
13623
		 */
13624
		private function get_complete_upgrade_instructions( $plan_title = '' ) {
13625
			if ( ! $this->has_premium_version() || $this->is_premium() ) {
13626
				return '';
13627
			}
13628
13629
			if ( empty( $plan_title ) ) {
13630
				$plan_title = $this->_site->plan->title;
13631
			}
13632
13633
			// @since 1.2.1.5 The free version is auto deactivated.
13634
			$deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ?
13635
				( '<li>' . $this->esc_html_inline( 'Deactivate the free version', 'deactivate-free-version' ) . '.</li>' ) :
13636
				'';
13637
13638
			return sprintf(
13639
				' %s: <ol><li>%s.</li>%s<li>%s (<a href="%s" target="_blank">%s</a>).</li></ol>',
13640
				$this->get_text_inline( 'Please follow these steps to complete the upgrade', 'follow-steps-to-complete-upgrade' ),
13641
				$this->get_latest_download_link( sprintf(
13642
					/* translators: %s: Plan title */
13643
					$this->get_text_inline( 'Download the latest %s version', 'download-latest-x-version' ),
13644
					$plan_title
13645
				) ),
13646
				$deactivation_step,
13647
				$this->get_text_inline( 'Upload and activate the downloaded version', 'upload-and-activate' ),
13648
				'//bit.ly/upload-wp-' . $this->_module_type . 's',
13649
				$this->get_text_inline( 'How to upload and activate?', 'howto-upload-activate' )
13650
			);
13651
		}
13652
13653
		/**
13654
		 * @author Vova Feldman (@svovaf)
13655
		 * @since  1.2.1.7
13656
		 *
13657
		 * @param string $key
13658
		 *
13659
		 * @return string
13660
		 */
13661
		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...
13662
			return fs_text( $key, $this->_slug );
13663
		}
13664
13665
		/**
13666
		 * @author Vova Feldman (@svovaf)
13667
		 * @since  1.2.3
13668
		 *
13669
         * @param string $text Translatable string.
13670
         * @param string $key  String key for overrides.
13671
		 *
13672
		 * @return string
13673
		 */
13674
		function get_text_inline( $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...
13675
			return _fs_text_inline( $text, $key, $this->_slug );
13676
		}
13677
13678
		/**
13679
		 * @author Vova Feldman (@svovaf)
13680
		 * @since  1.2.3
13681
		 *
13682
         * @param string $text Translatable string.
13683
		 * @param string $context Context information for the translators.
13684
         * @param string $key  String key for overrides.
13685
		 *
13686
		 * @return string
13687
		 */
13688
		function get_text_x_inline( $text, $context, $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...
13689
			return _fs_text_x_inline( $text, $context, $key, $this->_slug );
13690
		}
13691
13692
		/**
13693
		 * @author Vova Feldman (@svovaf)
13694
		 * @since  1.2.3
13695
		 *
13696
		 * @param string $text Translatable string.
13697
		 * @param string $key  String key for overrides.
13698
		 *
13699
		 * @return string
13700
		 */
13701
		function esc_html_inline( $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...
13702
            return esc_html( _fs_text_inline( $text, $key, $this->_slug ) );
13703
		}
13704
13705
		#----------------------------------------------------------------------------------
13706
		#region Versioning
13707
		#----------------------------------------------------------------------------------
13708
13709
		/**
13710
		 * Check if Freemius in SDK upgrade mode.
13711
		 *
13712
		 * @author Vova Feldman (@svovaf)
13713
		 * @since  1.0.9
13714
		 *
13715
		 * @return bool
13716
		 */
13717
		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...
13718
			return isset( $this->_storage->sdk_upgrade_mode ) ?
13719
				$this->_storage->sdk_upgrade_mode :
13720
				false;
13721
		}
13722
13723
		/**
13724
		 * Turn SDK upgrade mode off.
13725
		 *
13726
		 * @author Vova Feldman (@svovaf)
13727
		 * @since  1.0.9
13728
		 */
13729
		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...
13730
			$this->_storage->sdk_upgrade_mode = false;
13731
		}
13732
13733
		/**
13734
		 * Check if plugin upgrade mode.
13735
		 *
13736
		 * @author Vova Feldman (@svovaf)
13737
		 * @since  1.0.9
13738
		 *
13739
		 * @return bool
13740
		 */
13741
		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...
13742
			return isset( $this->_storage->plugin_upgrade_mode ) ?
13743
				$this->_storage->plugin_upgrade_mode :
13744
				false;
13745
		}
13746
13747
		/**
13748
		 * Turn plugin upgrade mode off.
13749
		 *
13750
		 * @author Vova Feldman (@svovaf)
13751
		 * @since  1.0.9
13752
		 */
13753
		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...
13754
			$this->_storage->plugin_upgrade_mode = false;
13755
		}
13756
13757
		#endregion
13758
13759
		#----------------------------------------------------------------------------------
13760
		#region Permissions
13761
		#----------------------------------------------------------------------------------
13762
13763
		/**
13764
		 * Check if specific permission requested.
13765
		 *
13766
		 * @author Vova Feldman (@svovaf)
13767
		 * @since  1.1.6
13768
		 *
13769
		 * @param string $permission
13770
		 *
13771
		 * @return bool
13772
		 */
13773
		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...
13774
			return isset( $this->_permissions[ $permission ] ) && ( true === $this->_permissions[ $permission ] );
13775
		}
13776
13777
		#endregion
13778
13779
		#----------------------------------------------------------------------------------
13780
		#region Auto Activation
13781
		#----------------------------------------------------------------------------------
13782
13783
		/**
13784
		 * Hints the SDK if running an auto-installation.
13785
		 *
13786
		 * @var bool
13787
		 */
13788
		private $_isAutoInstall = false;
13789
13790
		/**
13791
		 * After upgrade callback to install and auto activate a plugin.
13792
		 * This code will only be executed on explicit request from the user,
13793
		 * following the practice Jetpack are using with their theme installations.
13794
		 *
13795
		 * @link   https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
13796
		 *
13797
		 * @author Vova Feldman (@svovaf)
13798
		 * @since  1.2.1.7
13799
		 */
13800
		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...
13801
			$this->_logger->entrance();
13802
13803
			$this->check_ajax_referer( 'install_premium_version' );
13804
13805
			if ( ! $this->is_registered() ) {
13806
				// Not registered.
13807
				self::shoot_ajax_failure( array(
13808
					'message' => $this->get_text_inline( 'Auto installation only works for opted-in users.', 'auto-install-error-not-opted-in' ),
13809
					'code'    => 'premium_installed',
13810
				) );
13811
			}
13812
13813
			$plugin_id = fs_request_get( 'target_module_id', $this->get_id() );
13814
13815
			if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) {
13816
				// Invalid ID.
13817
				self::shoot_ajax_failure( array(
13818
					'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
13819
					'code'    => 'invalid_module_id',
13820
				) );
13821
			}
13822
13823
			if ( $plugin_id == $this->get_id() ) {
13824
				if ( $this->is_premium() ) {
13825
					// Already using the premium code version.
13826
					self::shoot_ajax_failure( array(
13827
						'message' => $this->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ),
13828
						'code'    => 'premium_installed',
13829
					) );
13830
				}
13831
				if ( ! $this->can_use_premium_code() ) {
13832
					// Don't have access to the premium code.
13833
					self::shoot_ajax_failure( array(
13834
						'message' => $this->get_text_inline( 'You do not have a valid license to access the premium version.', 'auto-install-error-invalid-license' ),
13835
						'code'    => 'invalid_license',
13836
					) );
13837
				}
13838
				if ( ! $this->has_release_on_freemius() ) {
13839
					// Plugin is a serviceware, no premium code version.
13840
					self::shoot_ajax_failure( array(
13841
						'message' => $this->get_text_inline( 'Plugin is a "Serviceware" which means it does not have a premium code version.', 'auto-install-error-serviceware' ),
13842
						'code'    => 'premium_version_missing',
13843
					) );
13844
				}
13845
			} else {
13846
				$addon = $this->get_addon( $plugin_id );
13847
13848
				if ( ! is_object( $addon ) ) {
13849
					// Invalid add-on ID.
13850
					self::shoot_ajax_failure( array(
13851
						'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
13852
						'code'    => 'invalid_module_id',
13853
					) );
13854
				}
13855
13856
				if ( $this->is_addon_activated( $plugin_id, true ) ) {
13857
					// Premium add-on version is already activated.
13858
					self::shoot_ajax_failure( array(
13859
						'message' => $this->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ),
13860
						'code'    => 'premium_installed',
13861
					) );
13862
				}
13863
			}
13864
13865
			$this->_isAutoInstall = true;
13866
13867
			// Try to install and activate.
13868
			$updater = new FS_Plugin_Updater( $this );
13869
			$result  = $updater->install_and_activate_plugin( $plugin_id );
13870
13871
			if ( is_array( $result ) && ! empty( $result['message'] ) ) {
13872
				self::shoot_ajax_failure( array(
13873
					'message' => $result['message'],
13874
					'code'    => $result['code'],
13875
				) );
13876
			}
13877
13878
			self::shoot_ajax_success( $result );
13879
		}
13880
13881
		/**
13882
		 * Displays module activation dialog box after a successful upgrade
13883
		 * where the user explicitly requested to auto download and install
13884
		 * the premium version.
13885
		 *
13886
		 * @author Vova Feldman (@svovaf)
13887
		 * @since  1.2.1.7
13888
		 */
13889
		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...
13890
			$this->_logger->entrance();
13891
13892
			if ( ! $this->is_registered() ) {
13893
				// Not registered.
13894
				return;
13895
			}
13896
13897
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
13898
13899
			if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) {
13900
				// Invalid module ID.
13901
				return;
13902
			}
13903
13904
			if ( $plugin_id == $this->get_id() ) {
13905
				if ( $this->is_premium() ) {
13906
					// Already using the premium code version.
13907
					return;
13908
				}
13909
				if ( ! $this->can_use_premium_code() ) {
13910
					// Don't have access to the premium code.
13911
					return;
13912
				}
13913
				if ( ! $this->has_release_on_freemius() ) {
13914
					// Plugin is a serviceware, no premium code version.
13915
					return;
13916
				}
13917
			} else {
13918
				$addon = $this->get_addon( $plugin_id );
13919
13920
				if ( ! is_object( $addon ) ) {
13921
					// Invalid add-on ID.
13922
					return;
13923
				}
13924
13925
				if ( $this->is_addon_activated( $plugin_id, true ) ) {
13926
					// Premium add-on version is already activated.
13927
					return;
13928
				}
13929
			}
13930
13931
            $vars = array(
13932
                'id'               => $this->_module_id,
13933
                'target_module_id' => $plugin_id,
13934
                'slug'             => $this->_slug,
13935
            );
13936
13937
			fs_require_template( 'auto-installation.php', $vars );
13938
		}
13939
13940
		#endregion
13941
13942
		#--------------------------------------------------------------------------------
13943
		#region Tabs Integration
13944
		#--------------------------------------------------------------------------------
13945
13946
		#region Module's Original Tabs
13947
13948
		/**
13949
		 * Inject a JavaScript logic to capture the theme tabs HTML.
13950
		 *
13951
		 * @author Vova Feldman (@svovaf)
13952
		 * @since  1.2.2.7
13953
		 */
13954
		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...
13955
			$this->_logger->entrance();
13956
13957
			if ( ! $this->is_theme_settings_page() ||
13958
			     ! $this->is_matching_url( $this->main_menu_url() )
13959
			) {
13960
				return;
13961
			}
13962
13963
			$params = array(
13964
				'id' => $this->_module_id,
13965
			);
13966
13967
			fs_require_once_template( 'tabs-capture-js.php', $params );
13968
		}
13969
13970
		/**
13971
		 * Cache theme's tabs HTML for a week. The cache will also be set as expired
13972
		 * after version and type (free/premium) changes, in addition to the week period.
13973
		 *
13974
		 * @author Vova Feldman (@svovaf)
13975
		 * @since  1.2.2.7
13976
		 */
13977
		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...
13978
			$this->_logger->entrance();
13979
13980
			$this->check_ajax_referer( 'store_tabs' );
13981
13982
			// Init filesystem if not yet initiated.
13983
			WP_Filesystem();
13984
13985
			// Get POST body HTML data.
13986
			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...
13987
			$tabs_html = $wp_filesystem->get_contents( "php://input" );
13988
13989
			if ( is_string( $tabs_html ) ) {
13990
				$tabs_html = trim( $tabs_html );
13991
			}
13992
13993
			if ( ! is_string( $tabs_html ) || empty( $tabs_html ) ) {
13994
				self::shoot_ajax_failure();
13995
			}
13996
13997
			$this->_cache->set( 'tabs', $tabs_html, 7 * WP_FS__TIME_24_HOURS_IN_SEC );
13998
13999
			self::shoot_ajax_success();
14000
		}
14001
14002
		/**
14003
		 * Cache theme's settings page custom styles. The cache will also be set as expired
14004
		 * after version and type (free/premium) changes, in addition to the week period.
14005
		 *
14006
		 * @author Vova Feldman (@svovaf)
14007
		 * @since  1.2.2.7
14008
		 */
14009
		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...
14010
			$this->_logger->entrance();
14011
14012
			if ( ! $this->is_theme_settings_page() ||
14013
			     ! $this->is_matching_url( $this->main_menu_url() )
14014
			) {
14015
				return;
14016
			}
14017
14018
			$wp_styles = wp_styles();
14019
14020
			$theme_styles_url = get_template_directory_uri();
14021
14022
			$stylesheets = array();
14023
			foreach ( $wp_styles->queue as $handler ) {
14024
				if ( fs_starts_with( $handler, 'fs_' ) ) {
14025
					// Assume that stylesheets that their handler starts with "fs_" belong to the SDK.
14026
					continue;
14027
				}
14028
14029
				/**
14030
				 * @var _WP_Dependency $stylesheet
14031
				 */
14032
				$stylesheet = $wp_styles->registered[ $handler ];
14033
14034
				if ( fs_starts_with( $stylesheet->src, $theme_styles_url ) ) {
14035
					$stylesheets[] = $stylesheet->src;
14036
				}
14037
			}
14038
14039
			if ( ! empty( $stylesheets ) ) {
14040
				$this->_cache->set( 'tabs_stylesheets', $stylesheets, 7 * WP_FS__TIME_24_HOURS_IN_SEC );
14041
			}
14042
		}
14043
14044
		/**
14045
		 * Check if module's original settings page has any tabs.
14046
		 *
14047
		 * @author Vova Feldman (@svovaf)
14048
		 * @since  1.2.2.7
14049
		 *
14050
		 * @return bool
14051
		 */
14052
		private function has_tabs() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
14053
			return $this->_cache->has( 'tabs' );
14054
		}
14055
14056
		/**
14057
		 * Get module's settings page HTML content, starting
14058
		 * from the beginning of the <div class="wrap"> element,
14059
		 * until the tabs HTML (including).
14060
		 *
14061
		 * @author Vova Feldman (@svovaf)
14062
		 * @since  1.2.2.7
14063
		 *
14064
		 * @return string
14065
		 */
14066
		private function get_tabs_html() {
14067
			$this->_logger->entrance();
14068
14069
			return $this->_cache->get( 'tabs' );
14070
		}
14071
14072
		/**
14073
		 * Check if page should include tabs.
14074
		 *
14075
		 * @author Vova Feldman (@svovaf)
14076
		 * @since  1.2.2.7
14077
		 *
14078
		 * @return bool
14079
		 */
14080
		private function should_page_include_tabs()
14081
		{
14082
			if ( ! $this->has_settings_menu() ) {
14083
				// Don't add tabs if no settings at all.
14084
				return false;
14085
			}
14086
14087
			if ( ! $this->is_theme() ) {
14088
				// Only add tabs to themes for now.
14089
				return false;
14090
			}
14091
14092
			if ( ! $this->has_paid_plan() && ! $this->has_addons() ) {
14093
				// Only add tabs to monetizing themes.
14094
				return false;
14095
			}
14096
14097
			if ( ! $this->is_theme_settings_page() ) {
14098
				// Only add tabs if browsing one of the theme's setting pages.
14099
				return false;
14100
			}
14101
14102
			if ( $this->is_admin_page( 'pricing' ) && fs_request_get_bool( 'checkout' ) ) {
14103
				// Don't add tabs on checkout page, we want to reduce distractions
14104
				// as much as possible.
14105
				return false;
14106
			}
14107
14108
			return true;
14109
		}
14110
14111
		/**
14112
		 * Add the tabs HTML before the setting's page content and
14113
		 * enqueue any required stylesheets.
14114
		 *
14115
		 * @author Vova Feldman (@svovaf)
14116
		 * @since  1.2.2.7
14117
		 *
14118
		 * @return bool If tabs were included.
14119
		 */
14120
		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...
14121
			$this->_logger->entrance();
14122
14123
			if ( ! $this->should_page_include_tabs() ) {
14124
				return false;
14125
			}
14126
14127
			/**
14128
			 * Enqueue the original stylesheets that are included in the
14129
			 * theme settings page. That way, if the theme settings has
14130
			 * some custom _styled_ content above the tabs UI, this
14131
			 * will make sure that the styling is preserved.
14132
			 */
14133
			$stylesheets = $this->_cache->get( 'tabs_stylesheets', array() );
14134
			if ( is_array( $stylesheets ) ) {
14135
				for ( $i = 0, $len = count( $stylesheets ); $i < $len; $i ++ ) {
14136
					wp_enqueue_style( "fs_{$this->_module_id}_tabs_{$i}", $stylesheets[ $i ] );
14137
				}
14138
			}
14139
14140
			// Cut closing </div> tag.
14141
			echo substr( trim( $this->get_tabs_html() ), 0, - 6 );
14142
14143
			return true;
14144
		}
14145
14146
		/**
14147
		 * Add the tabs closing HTML after the setting's page content.
14148
		 *
14149
		 * @author Vova Feldman (@svovaf)
14150
		 * @since  1.2.2.7
14151
		 *
14152
		 * @return bool If tabs closing HTML was included.
14153
		 */
14154
		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...
14155
			$this->_logger->entrance();
14156
14157
			if ( ! $this->should_page_include_tabs() ) {
14158
				return false;
14159
			}
14160
14161
			echo '</div>';
14162
14163
			return true;
14164
		}
14165
14166
		#endregion
14167
14168
		/**
14169
		 * Add in-page JavaScript to inject the Freemius tabs into
14170
		 * the module's setting tabs section.
14171
		 *
14172
		 * @author Vova Feldman (@svovaf)
14173
		 * @since  1.2.2.7
14174
		 */
14175
		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...
14176
			$this->_logger->entrance();
14177
14178
			if ( ! $this->should_page_include_tabs() ) {
14179
				return;
14180
			}
14181
14182
			$params = array( 'id' => $this->_module_id );
14183
			fs_require_once_template( 'tabs.php', $params );
14184
		}
14185
14186
		#endregion
14187
14188
		#--------------------------------------------------------------------------------
14189
		#region Customizer Integration for Themes
14190
		#--------------------------------------------------------------------------------
14191
14192
		/**
14193
		 * @author Vova Feldman (@svovaf)
14194
		 * @since  1.2.2.7
14195
		 *
14196
		 * @param WP_Customize_Manager $customizer
14197
		 */
14198
		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...
14199
			$this->_logger->entrance();
14200
14201
			if ( $this->is_pricing_page_visible() ) {
14202
				require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-upsell-control.php';
14203
14204
				$customizer->add_section( 'freemius_upsell', array(
14205
					'title'    => '&#9733; ' . $this->get_text_inline( 'View paid features', 'view-paid-features' ),
14206
					'priority' => 1,
14207
				) );
14208
				$customizer->add_setting( 'freemius_upsell', array(
14209
					'sanitize_callback' => 'esc_html',
14210
				) );
14211
14212
				$customizer->add_control( new FS_Customizer_Upsell_Control( $customizer, 'freemius_upsell', array(
14213
					'fs'       => $this,
14214
					'section'  => 'freemius_upsell',
14215
					'priority' => 100,
14216
				) ) );
14217
			}
14218
14219
			if ( $this->is_page_visible( 'contact' ) || $this->is_page_visible( 'support' ) ) {
14220
				require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-support-section.php';
14221
14222
				// Main Documentation Link In Customizer Root.
14223
				$customizer->add_section( new FS_Customizer_Support_Section( $customizer, 'freemius_support', array(
14224
					'fs'       => $this,
14225
					'priority' => 1000,
14226
				) ) );
14227
			}
14228
		}
14229
14230
		#endregion
14231
14232
		/**
14233
		 * If the theme has a paid version, add some custom
14234
		 * styling to the theme's premium version (if exists)
14235
		 * to highlight that it's the premium version of the
14236
		 * same theme, making it easier for identification
14237
		 * after the user upgrades and upload it to the site.
14238
		 *
14239
		 * @author Vova Feldman (@svovaf)
14240
		 * @since  1.2.2.7
14241
		 */
14242
		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...
14243
			$this->_logger->entrance();
14244
14245
			if ( ! self::is_themes_page() ) {
14246
				// Only include in the themes page.
14247
				return;
14248
			}
14249
14250
			if ( ! $this->has_paid_plan() ) {
14251
				// Only include if has any paid plans.
14252
				return;
14253
			}
14254
14255
			$params = null;
14256
			fs_require_once_template( '/js/jquery.content-change.php', $params );
14257
14258
			$params = array(
14259
				'slug' => $this->_slug,
14260
				'id'   => $this->_module_id,
14261
			);
14262
14263
			fs_require_template( '/js/style-premium-theme.php', $params );
14264
		}
14265
14266
		#----------------------------------------------------------------------------------
14267
		#region Marketing
14268
		#----------------------------------------------------------------------------------
14269
14270
		/**
14271
		 * Check if current user purchased any other plugins before.
14272
		 *
14273
		 * @author Vova Feldman (@svovaf)
14274
		 * @since  1.0.9
14275
		 *
14276
		 * @return bool
14277
		 */
14278
		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...
14279
			// TODO: Implement has_purchased_before() method.
14280
			throw new Exception( 'not implemented' );
14281
		}
14282
14283
		/**
14284
		 * Check if current user classified as an agency.
14285
		 *
14286
		 * @author Vova Feldman (@svovaf)
14287
		 * @since  1.0.9
14288
		 *
14289
		 * @return bool
14290
		 */
14291
		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...
14292
			// TODO: Implement is_agency() method.
14293
			throw new Exception( 'not implemented' );
14294
		}
14295
14296
		/**
14297
		 * Check if current user classified as a developer.
14298
		 *
14299
		 * @author Vova Feldman (@svovaf)
14300
		 * @since  1.0.9
14301
		 *
14302
		 * @return bool
14303
		 */
14304
		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...
14305
			// TODO: Implement is_developer() method.
14306
			throw new Exception( 'not implemented' );
14307
		}
14308
14309
		/**
14310
		 * Check if current user classified as a business.
14311
		 *
14312
		 * @author Vova Feldman (@svovaf)
14313
		 * @since  1.0.9
14314
		 *
14315
		 * @return bool
14316
		 */
14317
		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...
14318
			// TODO: Implement is_business() method.
14319
			throw new Exception( 'not implemented' );
14320
		}
14321
14322
		#endregion
14323
	}
14324