GitHub Access Token became invalid

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

Freemius   F

Complexity

Total Complexity 1491

Size/Duplication

Total Lines 12303
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 24

Importance

Changes 0
Metric Value
dl 0
loc 12303
rs 0.5665
c 0
b 0
f 0
wmc 1491
lcom 1
cbo 24

376 Methods

Rating   Name   Duplication   Size   Complexity  
A class-freemius.php ➔ fs_addons_body_class() 0 5 1
B __construct() 0 50 6
A has_settings_menu() 0 12 3
A is_submenu_item_visible() 0 7 2
D _version_updates_handler() 0 44 10
B _data_migration() 0 30 6
A _plugins_loaded() 0 8 2
C _register_hooks() 0 77 8
A unregister_uninstall_hook() 0 7 1
A clear_module_main_file_cache() 0 17 3
B _register_account_hooks() 0 26 5
C _find_caller_plugin_file() 0 52 8
C get_caller_main_file_and_type() 0 77 15
B _add_deactivation_feedback_dialog_box() 0 46 5
C _get_uninstall_reasons() 0 148 7
B _submit_uninstall_reason_action() 0 29 3
A instance() 0 17 4
A has_instance() 0 5 2
A get_instance_by_id() 0 9 3
A get_instance_by_file() 0 7 2
A get_parent_instance() 0 3 1
A get_addon_instance() 0 5 2
A is_parent_plugin_installed() 0 3 1
A is_parent_in_activation() 0 8 2
B is_activation_mode() 0 8 6
A is_activation_page() 0 12 3
B is_matching_url() 0 24 5
A get_active_plugins() 0 13 2
B get_all_plugins() 0 22 4
C get_plugin_slug() 0 33 11
B _load_required_static() 0 30 2
A _load_textdomain() 0 14 2
B _add_debug_section() 0 38 5
A _toggle_debug_mode() 0 12 3
B _get_debug_log() 0 9 5
A _get_db_option() 0 19 3
A _set_db_option() 0 10 2
B _debug_page_actions() 0 36 5
A _debug_page_render() 0 14 1
A is_on() 0 16 4
C should_run_connectivity_test() 0 47 11
A ping() 0 22 2
C has_api_connectivity() 0 40 13
C store_connectivity_info() 0 31 7
A turn_on() 0 16 3
B get_anonymous_id() 0 23 6
A _get_current_wp_user() 0 5 1
D _add_connectivity_issue_message() 0 217 18
B _email_about_firewall_issue() 0 65 5
A _retry_connectivity_test() 0 20 2
A _add_firewall_issues_javascript() 0 4 1
C send_email() 0 50 10
B get_email_sections() 0 99 4
A init() 0 10 1
F dynamic_init() 0 213 45
A _stop_tracking_callback() 0 20 4
A _allow_tracking_callback() 0 20 4
B stop_tracking() 0 35 6
B allow_tracking() 0 34 6
A reconnect_locally() 0 10 3
F parse_settings() 0 98 14
A get_option() 0 3 2
A get_bool_option() 0 3 3
A get_numeric_option() 0 3 3
C should_stop_execution() 0 67 9
A _after_code_type_change() 0 8 2
B _plugin_code_type_changed() 0 60 4
A is_addon_activated() 0 17 3
B is_addon_connected() 0 24 6
A is_addon_installed() 0 5 1
A get_addon_basename() 0 15 3
B get_installed_addons() 0 12 5
B has_installed_addons() 0 15 6
A init_addon() 0 3 1
A is_addon() 0 3 2
C deactivate_premium_only_addon_without_license() 0 53 10
A init_sandbox() 0 6 1
A is_payments_sandbox() 0 3 2
A is_live() 0 3 1
A is_anonymous() 0 19 4
A is_pending_activation() 0 3 1
A is_org_repo_compliant() 0 3 1
A run_manual_sync() 0 15 2
B _sync_cron() 0 30 5
A is_sync_executed() 0 7 2
A is_sync_cron_on() 0 8 2
A schedule_sync_cron() 0 22 2
A hook_callback_to_sync_cron() 0 3 1
A clear_sync_cron() 0 11 2
A next_sync_cron() 0 9 2
A last_sync_cron() 0 5 1
A is_install_sync_scheduled() 0 8 2
A schedule_install_sync() 0 18 1
A last_install_sync() 0 5 1
A next_install_sync() 0 9 2
A hook_callback_to_install_sync() 0 3 1
A clear_install_sync_cron() 0 11 2
A _run_sync_install() 0 8 1
A _add_pending_activation_notice() 0 17 3
A is_plugin_activation() 0 3 1
C _admin_init_action() 0 79 14
A _enqueue_connect_essentials() 0 9 1
B _add_connect_pointer_script() 0 41 1
A current_page_url() 0 17 4
A _is_plugin_page() 0 4 2
A _delete_site() 0 9 2
A _delete_plans() 0 9 1
A _delete_licenses() 0 13 2
A is_plugin_new_install() 0 4 2
A is_first_freemius_powered_version() 0 3 1
D _activate_plugin_event_hook() 0 97 15
B delete_account_event() 0 44 3
B _deactivate_plugin_hook() 0 41 6
A remove_sdk_reference() 0 12 3
A set_anonymous_mode() 0 12 1
A reset_anonymous_mode() 0 3 1
A connect_again() 0 9 2
A skip_connection() 0 13 1
A update_plugin_version_event() 0 10 2
D get_plugins_data_for_api() 0 103 14
D get_themes_data_for_api() 0 113 14
C get_install_data_for_api() 0 40 7
C send_install_update() 0 60 14
A sync_install() 0 21 3
D track_event() 0 34 9
A track_event_once() 0 3 1
C _uninstall_plugin_event() 0 35 8
A premium_plugin_basename() 0 3 1
B _uninstall_plugin_hook() 0 32 5
A require_plugin_essentials() 0 7 2
A require_pluggable_essentials() 0 5 2
A get_plugin_data() 0 19 2
A get_slug() 0 3 1
A get_target_folder_name() 0 3 2
A get_id() 0 3 1
A get_sdk_version() 0 3 1
A get_parent_id() 0 5 2
A get_public_key() 0 3 1
A get_secret_key() 0 3 1
A has_secret_key() 0 3 1
B get_plugin_name() 0 24 4
A get_plugin_version() 0 9 1
A get_plugin_title() 0 7 1
A get_plugin_basename() 0 3 1
A get_plugin_folder_name() 0 13 2
A find_slug_by_basename() 0 9 3
A store_file_slug_map() 0 14 4
A get_all_users() 0 9 2
A get_all_sites() 0 9 2
A get_all_licenses() 0 9 2
A get_all_plans() 0 9 2
A get_all_updates() 0 9 2
A get_all_addons() 0 9 2
A get_all_account_addons() 0 9 2
A is_registered() 0 3 1
A is_tracking_allowed() 0 3 2
A get_plugin() 0 3 1
A get_user() 0 3 1
A get_site() 0 3 1
A get_addons() 0 13 4
B get_account_addons() 0 15 5
A has_account_addons() 0 5 2
A get_addon() 0 15 4
A get_addon_by_slug() 0 15 4
A is_premium() 0 3 1
A get_plan_id() 0 3 1
A get_plan_title() 0 3 1
A get_plan() 0 5 2
A is_trial() 0 9 2
A is_paid_trial() 0 9 3
A is_trial_utilized() 0 9 2
A get_trial_plan() 0 9 2
B is_paying() 0 17 5
A is_free_plan() 0 14 4
A _has_premium_license() 0 7 1
A has_any_license() 0 3 2
B _get_available_premium_license() 0 17 6
A _sync_plans() 0 12 2
B _get_plan_by_id() 0 15 5
B get_plan_by_name() 0 15 5
A _sync_licenses() 0 15 3
B _get_license_by_id() 0 19 5
B _update_site_license() 0 35 6
B _sync_site_subscription() 0 20 5
A _get_license() 0 3 1
A _get_subscription() 0 5 2
C is_plan() 0 29 7
A is_single_plan() 0 12 4
D is_trial_plan() 0 36 9
A has_paid_plan() 0 4 2
A has_trial_plan() 0 18 3
A has_free_plan() 0 3 1
A _add_license_activation_dialog_box() 0 8 1
A _add_optout_dialog() 0 7 1
B _add_license_activation() 0 30 5
B _activate_license_ajax_action() 0 58 8
B _update_billing_ajax_action() 0 26 3
B _start_trial_ajax_action() 0 33 5
C _resend_license_key_ajax_action() 0 45 7
A is_plugins_page() 0 5 1
A is_themes_page() 0 5 1
A get_upgrade_url() 0 3 1
A get_trial_url() 0 3 1
A pricing_url() 0 13 2
A checkout_url() 0 23 2
A addon_checkout_url() 0 11 1
A has_addons() 0 5 1
A enable_anonymous() 0 3 1
A is_enable_anonymous() 0 3 1
A is_only_premium() 0 3 1
A is_plugin() 0 3 1
A has_premium_version() 0 3 1
A is_feature_supported() 0 3 1
A is_ssl() 0 3 1
A is_ajax() 0 3 2
C is_ajax_action() 0 28 8
D is_ajax_action_static() 0 30 9
A is_cron() 0 3 2
A is_user_in_admin() 0 3 3
A is_ssl_and_plan() 0 3 2
C _get_admin_page_url() 0 69 12
A _get_sync_license_url() 0 11 2
A get_account_url() 0 11 4
A get_account_tab_url() 0 5 1
A contact_url() 0 17 4
A addon_url() 0 5 1
A get_logger() 0 3 4
A get_options_manager() 0 3 4
A _encrypt() 0 23 2
A _decrypt() 0 23 2
A _encrypt_entity() 0 10 2
A _decrypt_entity() 0 10 2
C _activate_account() 0 45 7
A _get_user_by_email() 0 15 4
C _load_account() 0 65 16
A _set_account() 0 15 2
B get_opt_in_params() 0 66 6
F opt_in() 0 153 27
F setup_account() 0 121 19
B _install_with_new_user() 0 26 5
B install_with_new_user() 0 37 1
B set_pending_confirmation() 0 41 4
A _install_with_current_user() 0 19 4
B install_with_current_user() 0 61 6
A _activate_addon_account() 0 47 3
A get_menu_slug() 0 3 1
A _prepare_admin_menu() 0 14 3
B add_menu_action() 0 14 6
B _redirect_on_clicked_menu_link() 0 19 6
C override_plugin_menu_with_activation() 0 64 12
A get_top_level_menu_capability() 0 17 3
A get_top_level_menu_slug() 0 5 2
F add_submenu_items() 0 99 15
C embed_submenu_items() 0 51 9
B order_sub_submenu_items() 0 52 6
A _add_default_submenu_items() 0 17 4
B add_submenu_item() 0 48 6
B add_submenu_link_item() 0 39 5
A get_action_tag() 0 3 1
A get_action_tag_static() 0 3 2
A get_ajax_action() 0 3 1
A get_ajax_security() 0 3 1
A check_ajax_referer() 0 3 1
A get_ajax_action_static() 0 9 2
A do_action() 0 10 1
A add_action() 0 10 1
A add_ajax_action() 0 14 1
A add_ajax_action_static() 0 23 2
A shoot_ajax_response() 0 3 1
A shoot_ajax_success() 0 3 1
A shoot_ajax_failure() 0 8 2
A apply_filters() 0 8 1
A add_filter() 0 5 1
A has_filter() 0 5 1
A override_i18n() 0 3 1
A _store_site() 0 16 2
A _store_plans() 0 14 2
A _store_licenses() 0 18 3
A _store_user() 0 13 2
A _store_update() 0 15 3
A _store_addons() 0 7 1
A _delete_account_addons() 0 13 2
A _store_account_addons() 0 7 1
A _store_account() 0 10 1
B _handle_account_user_sync() 0 54 6
A _enrich_site_plan() 0 14 3
A _enrich_site_trial_plan() 0 14 3
B _fetch_site_license_subscription() 0 17 5
A _fetch_site_plan() 0 12 3
A _fetch_plugin_plans() 0 16 4
C _fetch_licenses() 0 76 17
A _fetch_payments() 0 20 4
A _fetch_billing() 0 11 2
A _update_plan() 0 6 1
A _update_licenses() 0 15 4
B _fetch_newer_version() 0 18 6
A get_update() 0 12 4
A has_active_license() 0 7 3
A has_active_valid_license() 0 8 4
A has_features_enabled_license() 0 7 3
A can_use_premium_code() 0 3 2
A is_user_admin() 0 3 1
A _sync_license() 0 15 3
C _sync_addon_license() 0 67 10
F _sync_plugin_license() 0 282 50
D _activate_license() 0 89 13
B _deactivate_license() 0 54 6
C _downgrade_site() 0 46 7
C start_trial() 0 85 8
C _cancel_trial() 0 70 7
A _is_addon_id() 0 3 2
A _can_download_premium() 0 4 3
B _get_latest_version_endpoint() 0 26 6
C _fetch_latest_version() 0 24 7
A download_latest_directly() 0 5 1
A get_latest_download_api_url() 0 7 1
A _get_invoice_api_url() 0 7 1
A get_latest_download_link() 0 7 1
A _get_latest_download_local_url() 0 10 2
B check_updates() 0 33 5
B sync_addons() 0 32 6
B update_email() 0 24 2
A is_api_error() 0 3 1
A is_api_result_object() 0 3 1
A is_api_result_entity() 0 3 1
A is_array_instanceof() 0 3 3
A init_change_owner() 0 14 1
B complete_change_owner() 0 28 2
A update_user_name() 0 20 2
B verify_email() 0 25 3
A get_activation_url() 0 11 2
A get_reconnect_url() 0 6 1
A get_after_activation_url() 0 10 2
D _handle_account_edits() 0 218 36
B _account_page_load() 0 38 6
A _account_page_render() 0 19 2
A _connect_page_render() 0 14 1
B _addons_page_load() 0 24 3
A _addons_page_render() 0 14 1
A _pricing_page_render() 0 11 2
A _contact_page_render() 0 6 1
A _hide_admin_notices() 0 6 1
A _clean_admin_content_section_hook() 0 6 1
A _clean_admin_content_section() 0 3 1
A get_api_user_scope() 0 14 3
A get_api_site_scope() 0 14 3
A get_api_plugin_scope() 0 13 2
A get_api_site_or_plugin_scope() 0 5 2
A _check_for_trial_plans() 0 3 1
A _fix_start_trial_menu_item_url() 0 4 1
D _add_trial_notice() 0 140 25
A _enqueue_common_css() 0 6 3
A hook_plugin_action_links() 0 17 1
A add_plugin_action_link() 0 18 3
B _add_upgrade_action_link() 0 25 5
B _add_license_action_link() 0 26 5
C _add_tracking_links() 0 69 13
C get_after_plugin_activation_redirect_url() 0 30 7
A _redirect_on_activation_hook() 0 7 2
C _modify_plugin_action_links_hook() 0 43 8
A add_admin_message() 0 3 1
A add_sticky_admin_message() 0 3 1
B get_complete_upgrade_instructions() 0 27 5
C _include_plugins_in_auto_update() 0 28 8
A get_text() 0 3 1
A is_sdk_upgrade_mode() 0 5 2
A set_sdk_upgrade_complete() 0 3 1
A is_plugin_upgrade_mode() 0 5 2
A set_plugin_upgrade_complete() 0 3 1
A is_permission_requested() 0 3 2
C _install_premium_version_ajax_action() 0 80 11
C _add_auto_installation_dialog_box() 0 49 9
A has_purchased_before() 0 4 1
A is_agency() 0 4 1
A is_developer() 0 4 1
A is_business() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Freemius often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Freemius, and based on these observations, apply Extract Interface, too.

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     http://opensource.org/licenses/gpl-2.0.php GNU Public License
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
		#endregion Plugin Info
72
73
		/**
74
		 * @since 1.0.9
75
		 *
76
		 * @var bool If false, don't turn Freemius on.
77
		 */
78
		private $_is_on;
79
80
		/**
81
		 * @since 1.1.3
82
		 *
83
		 * @var bool If false, don't turn Freemius on.
84
		 */
85
		private $_is_anonymous;
86
87
		/**
88
		 * @since 1.0.9
89
		 * @var bool If false, issues with connectivity to Freemius API.
90
		 */
91
		private $_has_api_connection;
92
93
		/**
94
		 * @since 1.0.9
95
		 * @var bool Hints the SDK if plugin can support anonymous mode (if skip connect is visible).
96
		 */
97
		private $_enable_anonymous;
98
99
		/**
100
		 * @since 1.1.7.5
101
		 * @var bool Hints the SDK if plugin should run in anonymous mode (only adds feedback form).
102
		 */
103
		private $_anonymous_mode;
104
105
		/**
106
		 * @since 1.1.9
107
		 * @var bool Hints the SDK if plugin have any free plans.
108
		 */
109
		private $_is_premium_only;
110
111
		/**
112
		 * @since 1.2.1.6
113
		 * @var bool Hints the SDK if plugin have premium code version at all.
114
		 */
115
		private $_has_premium_version;
116
117
		/**
118
		 * @since 1.2.1.6
119
		 * @var bool Hints the SDK if plugin should ignore pending mode by simulating a skip.
120
		 */
121
		private $_ignore_pending_mode;
122
123
		/**
124
		 * @since 1.0.8
125
		 * @var bool Hints the SDK if the plugin has any paid plans.
126
		 */
127
		private $_has_paid_plans;
128
129
		/**
130
		 * @since 1.2.1.5
131
		 * @var int Hints the SDK if the plugin offers a trial period. If negative, no trial, if zero - has a trial but
132
		 *      without a specified period, if positive - the number of trial days.
133
		 */
134
		private $_trial_days = - 1;
135
136
		/**
137
		 * @since 1.2.1.5
138
		 * @var bool Hints the SDK if the trial requires a payment method or not.
139
		 */
140
		private $_is_trial_require_payment = false;
141
142
		/**
143
		 * @since 1.0.7
144
		 * @var bool Hints the SDK if the plugin is WordPress.org compliant.
145
		 */
146
		private $_is_org_compliant;
147
148
		/**
149
		 * @since 1.0.7
150
		 * @var bool Hints the SDK if the plugin is has add-ons.
151
		 */
152
		private $_has_addons;
153
154
		/**
155
		 * @since 1.1.6
156
		 * @var string[]bool.
157
		 */
158
		private $_permissions;
159
160
		/**
161
		 * @var FS_Key_Value_Storage
162
		 */
163
		private $_storage;
164
165
		/**
166
		 * @since 1.0.0
167
		 *
168
		 * @var FS_Logger
169
		 */
170
		private $_logger;
171
		/**
172
		 * @since 1.0.4
173
		 *
174
		 * @var FS_Plugin
175
		 */
176
		private $_plugin = false;
177
		/**
178
		 * @since 1.0.4
179
		 *
180
		 * @var FS_Plugin|false
181
		 */
182
		private $_parent_plugin = false;
183
		/**
184
		 * @since 1.1.1
185
		 *
186
		 * @var Freemius
187
		 */
188
		private $_parent = false;
189
		/**
190
		 * @since 1.0.1
191
		 *
192
		 * @var FS_User
193
		 */
194
		private $_user = false;
195
		/**
196
		 * @since 1.0.1
197
		 *
198
		 * @var FS_Site
199
		 */
200
		private $_site = false;
201
		/**
202
		 * @since 1.0.1
203
		 *
204
		 * @var FS_Plugin_License
205
		 */
206
		private $_license;
207
		/**
208
		 * @since 1.0.2
209
		 *
210
		 * @var FS_Plugin_Plan[]
211
		 */
212
		private $_plans = false;
213
		/**
214
		 * @var FS_Plugin_License[]
215
		 * @since 1.0.5
216
		 */
217
		private $_licenses = false;
218
219
		/**
220
		 * @since 1.0.1
221
		 *
222
		 * @var FS_Admin_Menu_Manager
223
		 */
224
		private $_menu;
225
226
		/**
227
		 * @var FS_Admin_Notice_Manager
228
		 */
229
		private $_admin_notices;
230
231
		/**
232
		 * @since 1.1.6
233
		 *
234
		 * @var FS_Admin_Notice_Manager
235
		 */
236
		private static $_global_admin_notices;
237
238
		/**
239
		 * @var FS_Logger
240
		 * @since 1.0.0
241
		 */
242
		private static $_static_logger;
243
244
		/**
245
		 * @var FS_Option_Manager
246
		 * @since 1.0.2
247
		 */
248
		private static $_accounts;
249
250
		/**
251
		 * @var Freemius[]
252
		 */
253
		private static $_instances = array();
254
255
		#region Uninstall Reasons IDs
256
257
		const REASON_NO_LONGER_NEEDED = 1;
258
		const REASON_FOUND_A_BETTER_PLUGIN = 2;
259
		const REASON_NEEDED_FOR_A_SHORT_PERIOD = 3;
260
		const REASON_BROKE_MY_SITE = 4;
261
		const REASON_SUDDENLY_STOPPED_WORKING = 5;
262
		const REASON_CANT_PAY_ANYMORE = 6;
263
		const REASON_OTHER = 7;
264
		const REASON_DIDNT_WORK = 8;
265
		const REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION = 9;
266
		const REASON_COULDNT_MAKE_IT_WORK = 10;
267
		const REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE = 11;
268
		const REASON_NOT_WORKING = 12;
269
		const REASON_NOT_WHAT_I_WAS_LOOKING_FOR = 13;
270
		const REASON_DIDNT_WORK_AS_EXPECTED = 14;
271
		const REASON_TEMPORARY_DEACTIVATION = 15;
272
273
		#endregion
274
275
		/* Ctor
276
------------------------------------------------------------------------------------------------------------------*/
277
278
		/**
279
		 * Main singleton instance.
280
		 *
281
		 * @author Vova Feldman (@svovaf)
282
		 * @since  1.0.0
283
		 *
284
		 * @param string $slug
285
		 * @param bool   $is_init Since 1.2.1 Is initiation sequence.
286
		 */
287
		private function __construct( $slug, $is_init = false ) {
288
			$this->_slug = $slug;
289
290
			$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
291
292
			$this->_storage = FS_Key_Value_Storage::instance( 'plugin_data', $this->_slug );
293
294
			$this->_plugin_main_file_path = $this->_find_caller_plugin_file( $is_init );
295
			$this->_plugin_dir_path       = plugin_dir_path( $this->_plugin_main_file_path );
296
			$this->_plugin_basename       = plugin_basename( $this->_plugin_main_file_path );
297
			$this->_free_plugin_basename  = str_replace( '-premium/', '/', $this->_plugin_basename );
298
299
			$base_name_split        = explode( '/', $this->_plugin_basename );
300
			$this->_plugin_dir_name = $base_name_split[0];
301
302
			if ( $this->_logger->is_on() ) {
303
				$this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path );
304
				$this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path );
305
				$this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename );
306
				$this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename );
307
				$this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name );
308
			}
309
310
			// Remember link between file to slug.
311
			$this->store_file_slug_map();
312
313
			// Store plugin's initial install timestamp.
314
			if ( ! isset( $this->_storage->install_timestamp ) ) {
315
				$this->_storage->install_timestamp = WP_FS__SCRIPT_START_TIME;
316
			}
317
318
			$this->_plugin = FS_Plugin_Manager::instance( $this->_slug )->get();
0 ignored issues
show
Documentation Bug introduced by
It seems like \FS_Plugin_Manager::instance($this->_slug)->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...
319
320
			$this->_admin_notices = FS_Admin_Notice_Manager::instance(
321
				$slug,
322
				is_object( $this->_plugin ) ? $this->_plugin->title : ''
323
			);
324
325
			if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) ||
326
			     'true' === fs_request_is_action( 'restart_freemius' )
327
			) {
328
				FS_Api::clear_cache();
329
			}
330
331
			$this->_register_hooks();
332
333
			$this->_load_account();
334
335
			$this->_version_updates_handler();
336
		}
337
338
		/**
339
		 * Checks whether this plugin or theme has settings menu.
340
		 *
341
		 * @author Leo Fajardo (@leorw)
342
		 * @since  1.2.2
343
		 *
344
		 * @return bool
345
		 */
346
		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...
347
			/**
348
			 * At the moment the wp.org require to show the opt-in in
349
			 * the themes page. Therefore, if the theme is .org compliant,
350
			 * treat it as if it doesn't have a menu item.
351
			 */
352
			if ( $this->is_theme() && $this->is_org_repo_compliant() ) {
353
				return false;
354
			}
355
356
			return $this->_menu->has_menu();
357
		}
358
359
		/**
360
		 * Checks whether this a submenu item is visible.
361
		 *
362
		 * @author Vova Feldman (@svovaf)
363
		 * @since  1.2.2.6
364
		 *
365
		 * @param string $slug
366
		 *
367
		 * @return bool
368
		 */
369
		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...
370
			if ( ! $this->has_settings_menu() ) {
371
				return false;
372
			}
373
374
			return $this->_menu->is_submenu_item_visible( $slug );
375
		}
376
377
		/**
378
		 * @author Vova Feldman (@svovaf)
379
		 * @since  1.0.9
380
		 */
381
		private function _version_updates_handler() {
382
			if ( ! isset( $this->_storage->sdk_version ) || $this->_storage->sdk_version != $this->version ) {
383
				// Freemius version upgrade mode.
384
				$this->_storage->sdk_last_version = $this->_storage->sdk_version;
385
				$this->_storage->sdk_version      = $this->version;
386
387
				if ( empty( $this->_storage->sdk_last_version ) ||
388
				     version_compare( $this->_storage->sdk_last_version, $this->version, '<' )
389
				) {
390
					$this->_storage->sdk_upgrade_mode   = true;
391
					$this->_storage->sdk_downgrade_mode = false;
392
				} else {
393
					$this->_storage->sdk_downgrade_mode = true;
394
					$this->_storage->sdk_upgrade_mode   = false;
395
396
				}
397
398
				$this->do_action( 'sdk_version_update', $this->_storage->sdk_last_version, $this->version );
399
			}
400
401
			$plugin_version = $this->get_plugin_version();
402
			if ( ! isset( $this->_storage->plugin_version ) || $this->_storage->plugin_version != $plugin_version ) {
403
				// Plugin version upgrade mode.
404
				$this->_storage->plugin_last_version = $this->_storage->plugin_version;
405
				$this->_storage->plugin_version      = $plugin_version;
406
407
				if ( empty( $this->_storage->plugin_last_version ) ||
408
				     version_compare( $this->_storage->plugin_last_version, $plugin_version, '<' )
409
				) {
410
					$this->_storage->plugin_upgrade_mode   = true;
411
					$this->_storage->plugin_downgrade_mode = false;
412
				} else {
413
					$this->_storage->plugin_downgrade_mode = true;
414
					$this->_storage->plugin_upgrade_mode   = false;
415
				}
416
417
				if ( ! empty( $this->_storage->plugin_last_version ) ) {
418
					// Different version of the plugin was installed before, therefore it's an update.
419
					$this->_storage->is_plugin_new_install = false;
420
				}
421
422
				$this->do_action( 'plugin_version_update', $this->_storage->plugin_last_version, $plugin_version );
423
			}
424
		}
425
426
		/**
427
		 * @author Vova Feldman (@svovaf)
428
		 * @since  1.1.5
429
		 *
430
		 * @param string $sdk_prev_version
431
		 * @param string $sdk_version
432
		 */
433
		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...
434
			/**
435
			 * @since 1.1.7.3 Fixed unwanted connectivity test cleanup.
436
			 */
437
			if ( empty( $sdk_prev_version ) ) {
438
				return;
439
			}
440
441
			if ( version_compare( $sdk_prev_version, '1.1.5', '<' ) &&
442
			     version_compare( $sdk_version, '1.1.5', '>=' )
443
			) {
444
				// On version 1.1.5 merged connectivity and is_on data.
445
				if ( isset( $this->_storage->connectivity_test ) ) {
446
					if ( ! isset( $this->_storage->is_on ) ) {
447
						unset( $this->_storage->connectivity_test );
448
					} else {
449
						$connectivity_data              = $this->_storage->connectivity_test;
450
						$connectivity_data['is_active'] = $this->_storage->is_on['is_active'];
451
						$connectivity_data['timestamp'] = $this->_storage->is_on['timestamp'];
452
453
						// Override.
454
						$this->_storage->connectivity_test = $connectivity_data;
455
456
						// Remove previous structure.
457
						unset( $this->_storage->is_on );
458
					}
459
460
				}
461
			}
462
		}
463
464
		/**
465
		 * This action is connected to the 'plugins_loaded' hook and helps to determine
466
		 * if this is a new plugin installation or a plugin update.
467
		 *
468
		 * There are 3 different use-cases:
469
		 *    1) New plugin installation right with Freemius:
470
		 *       1.1 _activate_plugin_event_hook() will be executed first
471
		 *       1.2 Since $this->_storage->is_plugin_new_install is not set,
472
		 *           and $this->_storage->plugin_last_version is not set,
473
		 *           $this->_storage->is_plugin_new_install will be set to TRUE.
474
		 *       1.3 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
475
		 *           be already set to TRUE.
476
		 *
477
		 *    2) Plugin update, didn't have Freemius before, and now have the SDK:
478
		 *       2.1 _activate_plugin_event_hook() will not be executed, because
479
		 *           the activation hook do NOT fires on updates since WP 3.1.
480
		 *       2.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
481
		 *           be empty, therefore, it will be set to FALSE.
482
		 *
483
		 *    3) Plugin update, had Freemius in prev version as well:
484
		 *       3.1 _version_updates_handler() will be executed 1st, since FS was installed
485
		 *           before, $this->_storage->plugin_last_version will NOT be empty,
486
		 *           therefore, $this->_storage->is_plugin_new_install will be set to FALSE.
487
		 *       3.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install is
488
		 *           already set, therefore, it will not be modified.
489
		 *
490
		 *    Use-case #3 is backward compatible, #3.1 will be executed since 1.0.9.
491
		 *
492
		 * NOTE:
493
		 *    The only fallback of this mechanism is if an admin updates a plugin based on use-case #2,
494
		 *    and then, the next immediate PageView is the plugin's main settings page, it will not
495
		 *    show the opt-in right away. The reason it will happen is because Freemius execution
496
		 *    will be turned off till the plugin is fully loaded at least once
497
		 *    (till $this->_storage->was_plugin_loaded is TRUE).
498
		 *
499
		 * @author Vova Feldman (@svovaf)
500
		 * @since  1.1.9
501
		 *
502
		 */
503
		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...
504
			// Update flag that plugin was loaded with Freemius at least once.
505
			$this->_storage->was_plugin_loaded = true;
506
507
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
508
				$this->_storage->is_plugin_new_install = false;
509
			}
510
		}
511
512
		/**
513
		 * @author Vova Feldman (@svovaf)
514
		 * @since  1.0.9
515
		 */
516
		private function _register_hooks() {
517
			$this->_logger->entrance();
518
519
			if ( is_admin() ) {
520
				$plugin_dir = dirname( $this->_plugin_dir_path ) . '/';
521
522
				/**
523
				 * @since 1.2.2
524
				 *
525
				 * Hook to both free and premium version activations to support
526
				 * auto deactivation on the other version activation.
527
				 */
528
				register_activation_hook(
529
					$plugin_dir . $this->_free_plugin_basename,
530
					array( &$this, '_activate_plugin_event_hook' )
531
				);
532
				register_activation_hook(
533
					$plugin_dir . $this->premium_plugin_basename(),
534
					array( &$this, '_activate_plugin_event_hook' )
535
				);
536
537
				/**
538
				 * Part of the mechanism to identify new plugin install vs. plugin update.
539
				 *
540
				 * @author Vova Feldman (@svovaf)
541
				 * @since  1.1.9
542
				 */
543
				if ( empty( $this->_storage->was_plugin_loaded ) ) {
544
					if ( $this->is_activation_mode( false ) ) {
545
						add_action( 'plugins_loaded', array( &$this, '_plugins_loaded' ) );
546
					} else {
547
						// If was activated before, then it was already loaded before.
548
						$this->_plugins_loaded();
549
					}
550
				}
551
552
				if ( ! self::is_ajax() ) {
553
					if ( ! $this->is_addon() ) {
554
						add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY );
555
						add_action( 'admin_menu', array( &$this, '_prepare_admin_menu' ), WP_FS__LOWEST_PRIORITY );
556
					}
557
				}
558
			}
559
560
			register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) );
561
562
			add_action( 'init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY );
563
564
			add_action( 'admin_init', array( &$this, '_add_tracking_links' ) );
565
			add_action( 'admin_init', array( &$this, '_add_license_activation' ) );
566
			$this->add_ajax_action( 'update_billing', array( &$this, '_update_billing_ajax_action' ) );
567
			$this->add_ajax_action( 'start_trial', array( &$this, '_start_trial_ajax_action' ) );
568
569
			$this->add_ajax_action( 'install_premium_version', array(
570
				&$this,
571
				'_install_premium_version_ajax_action'
572
			) );
573
574
			$this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) );
575
576
			$this->add_action( 'sdk_version_update', array( &$this, '_data_migration' ), WP_FS__DEFAULT_PRIORITY, 2 );
577
578
			add_action( 'admin_init', array( &$this, '_add_trial_notice' ) );
579
			add_action( 'admin_init', array( &$this, '_enqueue_common_css' ) );
580
581
			/**
582
			 * Handle request to reset anonymous mode for `get_reconnect_url()`.
583
			 *
584
			 * @author Vova Feldman (@svovaf)
585
			 * @since  1.2.1.5
586
			 */
587
			if ( fs_request_is_action( 'reset_anonymous_mode' ) &&
588
			     $this->_slug === fs_request_get( 'fs_slug' )
589
			) {
590
				add_action( 'admin_init', array( &$this, 'connect_again' ) );
591
			}
592
		}
593
594
		/**
595
		 * Keeping the uninstall hook registered for free or premium plugin version may result to a fatal error that
596
		 * could happen when a user tries to uninstall either version while one of them is still active. Uninstalling a
597
		 * plugin will trigger inclusion of the free or premium version and if one of them is active during the
598
		 * uninstallation, a fatal error may occur in case the plugin's class or functions are already defined.
599
		 *
600
		 * @author Leo Fajardo (leorw)
601
		 *
602
		 * @since  1.2.0
603
		 */
604
		private function unregister_uninstall_hook() {
605
			$uninstallable_plugins = (array) get_option( 'uninstall_plugins' );
606
			unset( $uninstallable_plugins[ $this->_free_plugin_basename ] );
607
			unset( $uninstallable_plugins[ $this->premium_plugin_basename() ] );
608
609
			update_option( 'uninstall_plugins', $uninstallable_plugins );
610
		}
611
612
		/**
613
		 * @since 1.2.0 Invalidate module's main file cache, otherwise, FS_Plugin_Updater will not fetch updates.
614
		 */
615
		private function clear_module_main_file_cache() {
616
			if ( ! isset( $this->_storage->plugin_main_file ) ||
617
			     empty( $this->_storage->plugin_main_file->path )
618
			) {
619
				return;
620
			}
621
622
			$plugin_main_file = clone $this->_storage->plugin_main_file;
623
624
			// Store cached path (2nd layer cache).
625
			$plugin_main_file->prev_path = $plugin_main_file->path;
626
627
			// Clear cached path.
628
			unset( $plugin_main_file->path );
629
630
			$this->_storage->plugin_main_file = $plugin_main_file;
631
		}
632
633
		/**
634
		 * @author Vova Feldman (@svovaf)
635
		 * @since  1.0.9
636
		 */
637
		private function _register_account_hooks() {
638
			if ( ! is_admin() ) {
639
				return;
640
			}
641
642
			/**
643
			 * Always show the deactivation feedback form since we added
644
			 * automatic free version deactivation upon premium code activation.
645
			 *
646
			 * @since 1.2.1.6
647
			 */
648
			$this->add_ajax_action(
649
				'submit_uninstall_reason',
650
				array( &$this, '_submit_uninstall_reason_action' )
651
			);
652
653
			if ( $this->is_plugins_page() ) {
654
				add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) );
655
			}
656
657
			if ( ! $this->is_addon() ) {
658
				if ( $this->is_registered() ) {
659
					$this->add_filter( 'after_code_type_change', array( &$this, '_after_code_type_change' ) );
660
				}
661
			}
662
		}
663
664
		/**
665
		 * Leverage backtrace to find caller plugin file path.
666
		 *
667
		 * @author Vova Feldman (@svovaf)
668
		 * @since  1.0.6
669
		 *
670
		 * @param bool $is_init Is initiation sequence.
671
		 *
672
		 * @return string
673
		 *
674
		 * @uses   fs_find_caller_plugin_file
675
		 */
676
		private function _find_caller_plugin_file( $is_init = false ) {
677
			// Try to load the cached value of the file path.
678
			if ( isset( $this->_storage->plugin_main_file ) ) {
679
				$plugin_main_file = $this->_storage->plugin_main_file;
680
				if ( isset( $plugin_main_file->path ) && file_exists( $plugin_main_file->path ) ) {
681
					return $plugin_main_file->path;
682
				}
683
			}
684
685
			/**
686
			 * @since 1.2.1
687
			 *
688
			 * `clear_module_main_file_cache()` is clearing the plugin's cached path on
689
			 * deactivation. Therefore, if any plugin/theme was initiating `Freemius`
690
			 * with that plugin's slug, it was overriding the empty plugin path with a wrong path.
691
			 *
692
			 * So, we've added a special mechanism with a 2nd layer of cache that uses `prev_path`
693
			 * when the class instantiator isn't the module.
694
			 */
695
			if ( ! $is_init ) {
696
				// Fetch prev path cache.
697
				if ( isset( $this->_storage->plugin_main_file ) &&
698
				     isset( $this->_storage->plugin_main_file->prev_path )
699
				) {
700
					if ( file_exists( $this->_storage->plugin_main_file->prev_path ) ) {
701
						return $this->_storage->plugin_main_file->prev_path;
702
					}
703
				}
704
705
				wp_die(
706
					$this->get_text( 'failed-finding-main-path' ) .
707
					" Module: {$this->_slug}; SDK: " . WP_FS__SDK_VERSION . ";",
708
					$this->get_text( 'error' ),
709
					array( 'back_link' => true )
710
				);
711
			}
712
713
			/**
714
			 * @since 1.2.1
715
			 *
716
			 * Only the original instantiator that calls dynamic_init can modify the module's path.
717
			 */
718
			// Find caller module.
719
			$file_and_type = $this->get_caller_main_file_and_type();
720
			$plugin_file   = $file_and_type->path;
721
722
			$this->_storage->plugin_main_file = (object) array(
723
				'path' => fs_normalize_path( $plugin_file ),
724
			);
725
726
			return $plugin_file;
727
		}
728
729
		/**
730
		 * Identifies the caller path.
731
		 *
732
		 * @todo (Vova) When merging this branch with the theme's one, use the theme's one instead of this one.
733
		 *
734
		 * @author      Leo Fajardo (@leorw)
735
		 * @since       1.2.2
736
		 *
737
		 * @author      Vova Feldman (@svovaf)
738
		 * @since       1.2.2.3 Find the earliest module in the call stack that calls to the SDK. This fix is for cases
739
		 *              when add-ons are relying on loading the SDK from the parent module, and also allows themes
740
		 *              including the SDK an internal file instead of directly from functions.php.
741
		 * @since       1.2.1.7 Knows how to handle cases when an add-on includes the parent module logic.
742
		 */
743
		private function get_caller_main_file_and_type() {
744
			self::require_plugin_essentials();
745
746
			$all_plugins       = get_plugins();
747
			$all_plugins_paths = array();
748
749
			// Get active plugin's main files real full names (might be symlinks).
750
			foreach ( $all_plugins as $relative_path => &$data ) {
751
				if ( false === strpos( fs_normalize_path( $relative_path ), '/' ) ) {
752
					/**
753
					 * Ignore plugins that don't have a folder (e.g. Hello Dolly) since they
754
					 * can't really include the SDK.
755
					 *
756
					 * @author Vova Feldman
757
					 * @since  1.2.1.7
758
					 */
759
					continue;
760
				}
761
762
				$all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) );
763
			}
764
765
			$caller_file_candidate = false;
766
			$caller_map            = array();
767
			$module_type           = WP_FS__MODULE_TYPE_PLUGIN;
768
769
			for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) {
770
				if ( empty( $bt[ $i ]['file'] ) ) {
771
					continue;
772
				}
773
774
				if ( $i > 1 && ! empty( $bt[ $i - 1 ]['file'] ) && $bt[ $i ]['file'] === $bt[ $i - 1 ]['file'] ) {
775
					// If file same as the prev file in the stack, skip it.
776
					continue;
777
				}
778
779
				if ( ! empty( $bt[ $i ]['function'] ) && in_array( $bt[ $i ]['function'], array(
780
						'do_action',
781
						'apply_filter',
782
						'require_once',
783
						'require',
784
						'include_once',
785
						'include'
786
					) )
787
				) {
788
					// Ignore call stack hooks and files inclusion.
789
					continue;
790
				}
791
792
				$caller_file_path = fs_normalize_path( $bt[ $i ]['file'] );
793
				$caller_file_hash = md5( $caller_file_path );
794
795
				if ( ! isset( $caller_map[ $caller_file_hash ] ) ) {
796
					foreach ( $all_plugins_paths as $plugin_path ) {
797
						if ( false !== strpos( $caller_file_path, fs_normalize_path( dirname( $plugin_path ) . '/' ) ) ) {
798
							$caller_map[ $caller_file_hash ] = fs_normalize_path( $plugin_path );
799
							break;
800
						}
801
					}
802
				}
803
804
				if ( isset( $caller_map[ $caller_file_hash ] ) ) {
805
					$module_type           = WP_FS__MODULE_TYPE_PLUGIN;
806
					$caller_file_candidate = $caller_map[ $caller_file_hash ];
807
				}
808
			}
809
810
			if ( empty( $caller_file_candidate ) ) {
811
				// Throw an error to the developer in case of some edge case dev environment.
812
				wp_die( $this->get_text( 'failed-finding-main-path' ), $this->get_text( 'error' ), array( 'back_link' => true ) );
813
			}
814
815
			return (object) array(
816
				'module_type' => $module_type,
817
				'path'        => $caller_file_candidate
818
			);
819
		}
820
821
		#----------------------------------------------------------------------------------
822
		#region Deactivation Feedback Form
823
		#----------------------------------------------------------------------------------
824
825
		/**
826
		 * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins
827
		 * page.
828
		 *
829
		 * @author Vova Feldman (@svovaf)
830
		 * @author Leo Fajardo (@leorw)
831
		 * @since  1.1.2
832
		 */
833
		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...
834
			/* Check the type of user:
835
			 * 1. Long-term (long-term)
836
			 * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term).
837
			 * 3. Short-term (short-term)
838
			 */
839
			$is_long_term_user = true;
840
841
			// Check if the site is at least 2 days old.
842
			$time_installed = $this->_storage->install_timestamp;
843
844
			// Difference in seconds.
845
			$date_diff = time() - $time_installed;
846
847
			// Convert seconds to days.
848
			$date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) );
849
850
			if ( $date_diff_days < 2 ) {
851
				$is_long_term_user = false;
852
			}
853
854
			$is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user );
855
856
			if ( $is_long_term_user ) {
857
				$user_type = 'long-term';
858
			} else {
859
				if ( ! $this->is_registered() && ! $this->is_anonymous() ) {
860
					$user_type = 'non-registered-and-non-anonymous-short-term';
861
				} else {
862
					$user_type = 'short-term';
863
				}
864
			}
865
866
			$uninstall_reasons = $this->_get_uninstall_reasons( $user_type );
867
868
			// Load the HTML template for the deactivation feedback dialog box.
869
			$vars = array(
870
				'reasons' => $uninstall_reasons,
871
				'slug'    => $this->_slug
872
			);
873
874
			/**
875
			 * @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.
876
			 */
877
			fs_require_template( 'forms/deactivation/form.php', $vars );
878
		}
879
880
		/**
881
		 * @author Leo Fajardo (leorw)
882
		 * @since  1.1.2
883
		 *
884
		 * @param string $user_type
885
		 *
886
		 * @return array The uninstall reasons for the specified user type.
887
		 */
888
		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...
889
			$internal_message_template_var = array(
890
				'slug' => $this->_slug
891
			);
892
893
			if ( $this->is_registered() && false !== $this->get_plan() && $this->get_plan()->has_technical_support() ) {
894
				$contact_support_template = fs_get_template( 'forms/deactivation/contact.php', $internal_message_template_var );
895
			} else {
896
				$contact_support_template = '';
897
			}
898
899
			$reason_found_better_plugin = array(
900
				'id'                => self::REASON_FOUND_A_BETTER_PLUGIN,
901
				'text'              => $this->get_text( 'reason-found-a-better-plugin' ),
902
				'input_type'        => 'textfield',
903
				'input_placeholder' => $this->get_text( 'placeholder-plugin-name' )
904
			);
905
906
			$reason_temporary_deactivation = array(
907
				'id'                => self::REASON_TEMPORARY_DEACTIVATION,
908
				'text'              => $this->get_text( 'reason-temporary-deactivation' ),
909
				'input_type'        => '',
910
				'input_placeholder' => ''
911
			);
912
913
			$reason_other = array(
914
				'id'                => self::REASON_OTHER,
915
				'text'              => $this->get_text( 'reason-other' ),
916
				'input_type'        => 'textfield',
917
				'input_placeholder' => ''
918
			);
919
920
			$long_term_user_reasons = array(
921
				array(
922
					'id'                => self::REASON_NO_LONGER_NEEDED,
923
					'text'              => $this->get_text( 'reason-no-longer-needed' ),
924
					'input_type'        => '',
925
					'input_placeholder' => ''
926
				),
927
				$reason_found_better_plugin,
928
				array(
929
					'id'                => self::REASON_NEEDED_FOR_A_SHORT_PERIOD,
930
					'text'              => $this->get_text( 'reason-needed-for-a-short-period' ),
931
					'input_type'        => '',
932
					'input_placeholder' => ''
933
				),
934
				array(
935
					'id'                => self::REASON_BROKE_MY_SITE,
936
					'text'              => $this->get_text( 'reason-broke-my-site' ),
937
					'input_type'        => '',
938
					'input_placeholder' => '',
939
					'internal_message'  => $contact_support_template
940
				),
941
				array(
942
					'id'                => self::REASON_SUDDENLY_STOPPED_WORKING,
943
					'text'              => $this->get_text( 'reason-suddenly-stopped-working' ),
944
					'input_type'        => '',
945
					'input_placeholder' => '',
946
					'internal_message'  => $contact_support_template
947
				)
948
			);
949
950
			if ( $this->is_paying() ) {
951
				$long_term_user_reasons[] = array(
952
					'id'                => self::REASON_CANT_PAY_ANYMORE,
953
					'text'              => $this->get_text( 'reason-cant-pay-anymore' ),
954
					'input_type'        => 'textfield',
955
					'input_placeholder' => $this->get_text( 'placeholder-comfortable-price' )
956
				);
957
			}
958
959
			$reason_dont_share_info = array(
960
				'id'                => self::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION,
961
				'text'              => $this->get_text( 'reason-dont-like-to-share-my-information' ),
962
				'input_type'        => '',
963
				'input_placeholder' => ''
964
			);
965
966
			/**
967
			 * If the current user has selected the "don't share data" reason in the deactivation feedback modal, inform the
968
			 * user by showing additional message that he doesn't have to share data and can just choose to skip the opt-in
969
			 * (the Skip button is included in the message to show). This message will only be shown if anonymous mode is
970
			 * enabled and the user's account is currently not in pending activation state (similar to the way the Skip
971
			 * button in the opt-in form is shown/hidden).
972
			 */
973
			if ( $this->is_enable_anonymous() && ! $this->is_pending_activation() ) {
974
				$reason_dont_share_info['internal_message'] = fs_get_template( 'forms/deactivation/retry-skip.php', $internal_message_template_var );
975
			}
976
977
			$uninstall_reasons = array(
978
				'long-term'                                   => $long_term_user_reasons,
979
				'non-registered-and-non-anonymous-short-term' => array(
980
					array(
981
						'id'                => self::REASON_DIDNT_WORK,
982
						'text'              => $this->get_text( 'reason-didnt-work' ),
983
						'input_type'        => '',
984
						'input_placeholder' => ''
985
					),
986
					$reason_dont_share_info,
987
					$reason_found_better_plugin
988
				),
989
				'short-term'                                  => array(
990
					array(
991
						'id'                => self::REASON_COULDNT_MAKE_IT_WORK,
992
						'text'              => $this->get_text( 'reason-couldnt-make-it-work' ),
993
						'input_type'        => '',
994
						'input_placeholder' => '',
995
						'internal_message'  => $contact_support_template
996
					),
997
					$reason_found_better_plugin,
998
					array(
999
						'id'                => self::REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE,
1000
						'text'              => $this->get_text( 'reason-great-but-need-specific-feature' ),
1001
						'input_type'        => 'textarea',
1002
						'input_placeholder' => $this->get_text( 'placeholder-feature' )
1003
					),
1004
					array(
1005
						'id'                => self::REASON_NOT_WORKING,
1006
						'text'              => $this->get_text( 'reason-not-working' ),
1007
						'input_type'        => 'textarea',
1008
						'input_placeholder' => $this->get_text( 'placeholder-share-what-didnt-work' )
1009
					),
1010
					array(
1011
						'id'                => self::REASON_NOT_WHAT_I_WAS_LOOKING_FOR,
1012
						'text'              => $this->get_text( 'reason-not-what-i-was-looking-for' ),
1013
						'input_type'        => 'textarea',
1014
						'input_placeholder' => $this->get_text( 'placeholder-what-youve-been-looking-for' )
1015
					),
1016
					array(
1017
						'id'                => self::REASON_DIDNT_WORK_AS_EXPECTED,
1018
						'text'              => $this->get_text( 'reason-didnt-work-as-expected' ),
1019
						'input_type'        => 'textarea',
1020
						'input_placeholder' => $this->get_text( 'placeholder-what-did-you-expect' )
1021
					)
1022
				)
1023
			);
1024
1025
			// Randomize the reasons for the current user type.
1026
			shuffle( $uninstall_reasons[ $user_type ] );
1027
1028
			// Keep the following reasons as the last items in the list.
1029
			$uninstall_reasons[ $user_type ][] = $reason_temporary_deactivation;
1030
			$uninstall_reasons[ $user_type ][] = $reason_other;
1031
1032
			$uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons );
1033
1034
			return $uninstall_reasons[ $user_type ];
1035
		}
1036
1037
		/**
1038
		 * Called after the user has submitted his reason for deactivating the plugin.
1039
		 *
1040
		 * @author Leo Fajardo (@leorw)
1041
		 * @since  1.1.2
1042
		 */
1043
		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...
1044
			$this->_logger->entrance();
1045
1046
			$this->check_ajax_referer( 'submit_uninstall_reason' );
1047
1048
			$reason_id = fs_request_get( 'reason_id' );
1049
1050
			// Check if the given reason ID is an unsigned integer.
1051
			if ( ! ctype_digit( $reason_id ) ) {
1052
				exit;
1053
			}
1054
1055
			$reason_info = trim( fs_request_get( 'reason_info', '' ) );
1056
			if ( ! empty( $reason_info ) ) {
1057
				$reason_info = substr( $reason_info, 0, 128 );
1058
			}
1059
1060
			$reason = (object) array(
1061
				'id'           => $reason_id,
1062
				'info'         => $reason_info,
1063
				'is_anonymous' => fs_request_get_bool( 'is_anonymous' )
1064
			);
1065
1066
			$this->_storage->store( 'uninstall_reason', $reason );
1067
1068
			// Print '1' for successful operation.
1069
			echo 1;
1070
			exit;
1071
		}
1072
1073
		#endregion
1074
1075
		#----------------------------------------------------------------------------------
1076
		#region Instance
1077
		#----------------------------------------------------------------------------------
1078
1079
		/**
1080
		 * Main singleton instance.
1081
		 *
1082
		 * @author Vova Feldman (@svovaf)
1083
		 * @since  1.0.0
1084
		 *
1085
		 * @param string $slug
1086
		 * @param bool   $is_init Is initiation sequence.
1087
		 *
1088
		 * @return Freemius|false
1089
		 */
1090
		static function instance( $slug, $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...
1091
			if ( empty( $slug ) ) {
1092
				return false;
1093
			}
1094
1095
			$slug = strtolower( $slug );
1096
1097
			if ( ! isset( self::$_instances[ $slug ] ) ) {
1098
				if ( 0 === count( self::$_instances ) ) {
1099
					self::_load_required_static();
1100
				}
1101
1102
				self::$_instances[ $slug ] = new Freemius( $slug, $is_init );
1103
			}
1104
1105
			return self::$_instances[ $slug ];
1106
		}
1107
1108
		/**
1109
		 * @author Vova Feldman (@svovaf)
1110
		 * @since  1.0.6
1111
		 *
1112
		 * @param string|number $slug_or_id
1113
		 *
1114
		 * @return bool
1115
		 */
1116
		private static function has_instance( $slug_or_id ) {
1117
			return ! is_numeric( $slug_or_id ) ?
1118
				isset( self::$_instances[ strtolower( $slug_or_id ) ] ) :
1119
				( false !== self::get_instance_by_id( $slug_or_id ) );
1120
		}
1121
1122
		/**
1123
		 * @author Vova Feldman (@svovaf)
1124
		 * @since  1.0.6
1125
		 *
1126
		 * @param number $id
1127
		 *
1128
		 * @return false|Freemius
1129
		 */
1130
		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...
1131
			foreach ( self::$_instances as $slug => $instance ) {
1132
				if ( $id == $instance->get_id() ) {
1133
					return $instance;
1134
				}
1135
			}
1136
1137
			return false;
1138
		}
1139
1140
		/**
1141
		 *
1142
		 * @author Vova Feldman (@svovaf)
1143
		 * @since  1.0.1
1144
		 *
1145
		 * @param $plugin_file
1146
		 *
1147
		 * @return false|Freemius
1148
		 */
1149
		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...
1150
			$slug = self::find_slug_by_basename( $plugin_file );
1151
1152
			return ( false !== $slug ) ?
1153
				self::instance( $slug ) :
1154
				false;
1155
		}
1156
1157
		/**
1158
		 * @author Vova Feldman (@svovaf)
1159
		 * @since  1.0.6
1160
		 *
1161
		 * @return false|Freemius
1162
		 */
1163
		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...
1164
			return self::get_instance_by_id( $this->_plugin->parent_plugin_id );
1165
		}
1166
1167
		/**
1168
		 * @author Vova Feldman (@svovaf)
1169
		 * @since  1.0.6
1170
		 *
1171
		 * @param string|number $slug_or_id
1172
		 *
1173
		 * @return bool|Freemius
1174
		 */
1175
		function get_addon_instance( $slug_or_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...
1176
			return ! is_numeric( $slug_or_id ) ?
1177
				self::instance( strtolower( $slug_or_id ) ) :
1178
				self::get_instance_by_id( $slug_or_id );
1179
		}
1180
1181
		#endregion ------------------------------------------------------------------
1182
1183
		/**
1184
		 * @author Vova Feldman (@svovaf)
1185
		 * @since  1.0.6
1186
		 *
1187
		 * @return bool
1188
		 */
1189
		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...
1190
			return self::has_instance( $this->_plugin->parent_plugin_id );
1191
		}
1192
1193
		/**
1194
		 * Check if add-on parent plugin in activation mode.
1195
		 *
1196
		 * @author Vova Feldman (@svovaf)
1197
		 * @since  1.0.7
1198
		 *
1199
		 * @return bool
1200
		 */
1201
		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...
1202
			$parent_fs = $this->get_parent_instance();
1203
			if ( ! is_object( $parent_fs ) ) {
1204
				return false;
1205
			}
1206
1207
			return ( $parent_fs->is_activation_mode() );
1208
		}
1209
1210
		/**
1211
		 * Is plugin in activation mode.
1212
		 *
1213
		 * @author Vova Feldman (@svovaf)
1214
		 * @since  1.0.7
1215
		 *
1216
		 * @param bool $and_on
1217
		 *
1218
		 * @return bool
1219
		 */
1220
		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...
1221
			return (
1222
				( $this->is_on() || ! $and_on ) &&
1223
				! $this->is_registered() &&
1224
				( ! $this->is_enable_anonymous() ||
1225
				  ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) )
1226
			);
1227
		}
1228
1229
		/**
1230
		 * Check if current page is the opt-in/pending-activation page.
1231
		 *
1232
		 * @author Vova Feldman (@svovaf)
1233
		 * @since  1.2.1.7
1234
		 *
1235
		 * @return bool
1236
		 */
1237
		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...
Coding Style introduced by
is_activation_page 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...
1238
			if ( $this->_menu->is_main_settings_page() ) {
1239
				return true;
1240
			}
1241
1242
			if ( ! $this->is_activation_mode() ) {
1243
				return false;
1244
			}
1245
1246
			// Check if current page is matching the activation page.
1247
			return $this->is_matching_url( $_SERVER['REQUEST_URI'], $this->get_activation_url() );
1248
		}
1249
1250
		/**
1251
		 * Check if URL path's are matching and that all querystring
1252
		 * arguments of the $sub_url exist in the $url with the same values.
1253
		 *
1254
		 * WARNING:
1255
		 *  1. This method doesn't check if the sub/domain are matching.
1256
		 *  2. Ignore case sensitivity.
1257
		 *
1258
		 * @author Vova Feldman (@svovaf)
1259
		 * @since  1.2.1.7
1260
		 *
1261
		 * @param string $url
1262
		 * @param string $sub_url
1263
		 *
1264
		 * @return bool
1265
		 */
1266
		private function is_matching_url( $url, $sub_url ) {
1267
			$url     = strtolower( $url );
1268
			$sub_url = strtolower( $sub_url );
1269
1270
			if ( parse_url( $sub_url, PHP_URL_PATH ) !== parse_url( $url, PHP_URL_PATH ) ) {
1271
				// Different path - DO NOT OVERRIDE PAGE.
1272
				return false;
1273
			}
1274
1275
			$url_params = array();
1276
			parse_str( parse_url( $url, PHP_URL_QUERY ), $url_params );
1277
1278
			$sub_url_params = array();
1279
			parse_str( parse_url( $sub_url, PHP_URL_QUERY ), $sub_url_params );
1280
1281
			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...
1282
				if ( ! isset( $url_params[ $key ] ) || $val != $url_params[ $key ] ) {
1283
					// Not matching query string - DO NOT OVERRIDE PAGE.
1284
					return false;
1285
				}
1286
			}
1287
1288
			return true;
1289
		}
1290
1291
		/**
1292
		 * Get collection of all active plugins.
1293
		 *
1294
		 * @author Vova Feldman (@svovaf)
1295
		 * @since  1.0.9
1296
		 *
1297
		 * @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...
1298
		 */
1299
		private static function get_active_plugins() {
1300
			self::require_plugin_essentials();
1301
1302
			$active_plugin            = array();
1303
			$all_plugins              = get_plugins();
1304
			$active_plugins_basenames = get_option( 'active_plugins' );
1305
1306
			foreach ( $active_plugins_basenames as $plugin_basename ) {
1307
				$active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ];
1308
			}
1309
1310
			return $active_plugin;
1311
		}
1312
1313
		/**
1314
		 * Get collection of all plugins.
1315
		 *
1316
		 * @author Vova Feldman (@svovaf)
1317
		 * @since  1.1.8
1318
		 *
1319
		 * @return array Key is the plugin file path and the value is an array of the plugin data.
1320
		 */
1321
		private static function get_all_plugins() {
1322
			self::require_plugin_essentials();
1323
1324
			$all_plugins              = get_plugins();
1325
			$active_plugins_basenames = get_option( 'active_plugins' );
1326
1327
			foreach ( $all_plugins as $basename => &$data ) {
1328
				// By default set to inactive (next foreach update the active plugins).
1329
				$data['is_active'] = false;
1330
				// Enrich with plugin slug.
1331
				$data['slug'] = self::get_plugin_slug( $basename );
1332
			}
1333
1334
			// Flag active plugins.
1335
			foreach ( $active_plugins_basenames as $basename ) {
1336
				if ( isset( $all_plugins[ $basename ] ) ) {
1337
					$all_plugins[ $basename ]['is_active'] = true;
1338
				}
1339
			}
1340
1341
			return $all_plugins;
1342
		}
1343
1344
1345
		/**
1346
		 * Cached result of get_site_transient( 'update_plugins' )
1347
		 *
1348
		 * @author Vova Feldman (@svovaf)
1349
		 * @since  1.1.8
1350
		 *
1351
		 * @var object
1352
		 */
1353
		private static $_plugins_info;
1354
1355
		/**
1356
		 * Helper function to get specified plugin's slug.
1357
		 *
1358
		 * @author Vova Feldman (@svovaf)
1359
		 * @since  1.1.8
1360
		 *
1361
		 * @param $basename
1362
		 *
1363
		 * @return string
1364
		 */
1365
		private static function get_plugin_slug( $basename ) {
1366
			if ( ! isset( self::$_plugins_info ) ) {
1367
				self::$_plugins_info = get_site_transient( 'update_plugins' );
1368
			}
1369
1370
			$slug = '';
1371
1372
			if ( is_object( self::$_plugins_info ) ) {
1373
				if ( isset( self::$_plugins_info->no_update ) &&
1374
				     isset( self::$_plugins_info->no_update[ $basename ] ) &&
1375
				     ! empty( self::$_plugins_info->no_update[ $basename ]->slug )
1376
				) {
1377
					$slug = self::$_plugins_info->no_update[ $basename ]->slug;
1378
				} else if ( isset( self::$_plugins_info->response ) &&
1379
				            isset( self::$_plugins_info->response[ $basename ] ) &&
1380
				            ! empty( self::$_plugins_info->response[ $basename ]->slug )
1381
				) {
1382
					$slug = self::$_plugins_info->response[ $basename ]->slug;
1383
				}
1384
			}
1385
1386
			if ( empty( $slug ) ) {
1387
				// Try to find slug from FS data.
1388
				$slug = self::find_slug_by_basename( $basename );
1389
			}
1390
1391
			if ( empty( $slug ) ) {
1392
				// Fallback to plugin's folder name.
1393
				$slug = dirname( $basename );
1394
			}
1395
1396
			return $slug;
1397
		}
1398
1399
		private static $_statics_loaded = false;
1400
1401
		/**
1402
		 * Load static resources.
1403
		 *
1404
		 * @author Vova Feldman (@svovaf)
1405
		 * @since  1.0.1
1406
		 */
1407
		private static function _load_required_static() {
1408
			if ( self::$_statics_loaded ) {
1409
				return;
1410
			}
1411
1412
			self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
1413
1414
			self::$_static_logger->entrance();
1415
1416
			self::$_accounts = FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true );
1417
1418
			self::$_global_admin_notices = FS_Admin_Notice_Manager::instance( 'global' );
1419
1420
			// Configure which Freemius powered plugins should be auto updated.
1421
//			add_filter( 'auto_update_plugin', '_include_plugins_in_auto_update', 10, 2 );
1422
1423
			add_action( 'admin_menu', array( 'Freemius', '_add_debug_section' ) );
1424
1425
			add_action( "wp_ajax_fs_toggle_debug_mode", array( 'Freemius', '_toggle_debug_mode' ) );
1426
1427
			self::add_ajax_action_static( 'get_debug_log', array( 'Freemius', '_get_debug_log' ) );
1428
1429
			self::add_ajax_action_static( 'get_db_option', array( 'Freemius', '_get_db_option' ) );
1430
1431
			self::add_ajax_action_static( 'set_db_option', array( 'Freemius', '_set_db_option' ) );
1432
1433
			add_action( 'plugins_loaded', array( 'Freemius', '_load_textdomain' ), 1 );
1434
1435
			self::$_statics_loaded = true;
1436
		}
1437
1438
		#----------------------------------------------------------------------------------
1439
		#region Localization
1440
		#----------------------------------------------------------------------------------
1441
1442
		/**
1443
		 * Load framework's text domain.
1444
		 *
1445
		 * @author Vova Feldman (@svovaf)
1446
		 * @since  1.2.1
1447
		 */
1448
		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...
1449
			if ( ! is_admin() ) {
1450
				return;
1451
			}
1452
1453
			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...
1454
1455
			// Works both for plugins and themes.
1456
			load_plugin_textdomain(
1457
				'freemius',
1458
				false,
1459
				$fs_active_plugins->newest->sdk_path . '/languages/'
1460
			);
1461
		}
1462
1463
		#endregion
1464
1465
		#----------------------------------------------------------------------------------
1466
		#region Debugging
1467
		#----------------------------------------------------------------------------------
1468
1469
		/**
1470
		 * @author Vova Feldman (@svovaf)
1471
		 * @since  1.0.8
1472
		 */
1473
		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...
1474
			if ( ! current_user_can( 'activate_plugins' ) ) {
1475
				return;
1476
			}
1477
1478
			self::$_static_logger->entrance();
1479
1480
			$title = sprintf( '%s [v.%s]', fs_text( 'freemius-debug' ), WP_FS__SDK_VERSION );
1481
1482
			$hook = null;
1483
1484
			if ( WP_FS__DEV_MODE ) {
1485
				// Add top-level debug menu item.
1486
				$hook = FS_Admin_Menu_Manager::add_page(
1487
					$title,
1488
					$title,
1489
					'manage_options',
1490
					'freemius',
1491
					array( 'Freemius', '_debug_page_render' )
1492
				);
1493
			} else {
1494
				if ( 'freemius' === fs_request_get( 'page' ) ) {
1495
					// Add hidden debug page.
1496
					$hook = FS_Admin_Menu_Manager::add_subpage(
1497
						null,
1498
						$title,
1499
						$title,
1500
						'manage_options',
1501
						'freemius',
1502
						array( 'Freemius', '_debug_page_render' )
1503
					);
1504
				}
1505
			}
1506
1507
			if ( ! empty( $hook ) ) {
1508
				add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) );
1509
			}
1510
		}
1511
1512
		/**
1513
		 * @author Vova Feldman (@svovaf)
1514
		 * @since  1.1.7.3
1515
		 */
1516
		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...
1517
			$is_on = fs_request_get( 'is_on', false, 'post' );
1518
1519
			if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) {
1520
				update_option( 'fs_debug_mode', $is_on );
1521
1522
				// Turn on/off storage logging.
1523
				FS_Logger::_set_storage_logging( ( 1 == $is_on ) );
1524
			}
1525
1526
			exit;
1527
		}
1528
1529
		/**
1530
		 * @author Vova Feldman (@svovaf)
1531
		 * @since  1.2.1.6
1532
		 */
1533
		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...
1534
			$logs = FS_Logger::load_db_logs(
1535
				fs_request_get( 'filters', false, 'post' ),
1536
				! empty( $_POST['limit'] ) && is_numeric( $_POST['limit'] ) ? $_POST['limit'] : 200,
1537
				! empty( $_POST['offset'] ) && is_numeric( $_POST['offset'] ) ? $_POST['offset'] : 0
1538
			);
1539
1540
			self::shoot_ajax_success( $logs );
1541
		}
1542
1543
		/**
1544
		 * @author Vova Feldman (@svovaf)
1545
		 * @since  1.2.1.7
1546
		 */
1547
		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...
1548
			$option_name = fs_request_get( 'option_name' );
1549
1550
			$value = get_option( $option_name );
1551
1552
			$result = array(
1553
				'name' => $option_name,
1554
			);
1555
1556
			if ( false !== $value ) {
1557
				if ( ! is_string( $value ) ) {
1558
					$value = json_encode( $value );
1559
				}
1560
1561
				$result['value'] = $value;
1562
			}
1563
1564
			self::shoot_ajax_success( $result );
1565
		}
1566
1567
		/**
1568
		 * @author Vova Feldman (@svovaf)
1569
		 * @since  1.2.1.7
1570
		 */
1571
		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...
1572
			$option_name  = fs_request_get( 'option_name' );
1573
			$option_value = fs_request_get( 'option_value' );
1574
1575
			if ( ! empty( $option_value ) ) {
1576
				update_option( $option_name, $option_value );
1577
			}
1578
1579
			self::shoot_ajax_success();
1580
		}
1581
1582
1583
		/**
1584
		 * @author Vova Feldman (@svovaf)
1585
		 * @since  1.0.8
1586
		 */
1587
		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...
1588
			self::_clean_admin_content_section();
1589
1590
			if ( fs_request_is_action( 'restart_freemius' ) ) {
1591
				check_admin_referer( 'restart_freemius' );
1592
1593
				// Clear accounts data.
1594
				self::$_accounts->clear( true );
1595
1596
				// Clear SDK reference cache.
1597
				delete_option( 'fs_active_plugins' );
1598
			} else if ( fs_request_is_action( 'simulate_trial' ) ) {
1599
				check_admin_referer( 'simulate_trial' );
1600
1601
				$slug = fs_request_get( 'slug' );
1602
1603
				$fs = freemius( $slug );
1604
1605
				// Update SDK install to at least 24 hours before.
1606
				$fs->_storage->install_timestamp = ( time() - WP_FS__TIME_24_HOURS_IN_SEC );
1607
				// Unset the trial shown timestamp.
1608
				unset( $fs->_storage->trial_promotion_shown );
1609
			} else if ( fs_request_is_action( 'download_logs' ) ) {
1610
				check_admin_referer( 'download_logs' );
1611
1612
				$download_url = FS_Logger::download_db_logs(
1613
					fs_request_get( 'filters', false, 'post' )
1614
				);
1615
1616
				if ( false === $download_url ) {
1617
					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].' );
1618
				}
1619
1620
				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 1612 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...
1621
			}
1622
		}
1623
1624
		/**
1625
		 * @author Vova Feldman (@svovaf)
1626
		 * @since  1.0.8
1627
		 */
1628
		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...
1629
			self::$_static_logger->entrance();
1630
1631
			$vars = array(
1632
				'sites'          => self::get_all_sites(),
1633
				'users'          => self::get_all_users(),
1634
				'addons'         => self::get_all_addons(),
1635
				'account_addons' => self::get_all_account_addons(),
1636
				'licenses'       => self::get_all_licenses(),
1637
			);
1638
1639
			fs_enqueue_local_style( 'fs_debug', '/admin/debug.css' );
1640
			fs_require_once_template( 'debug.php', $vars );
1641
		}
1642
1643
		#endregion
1644
1645
		#----------------------------------------------------------------------------------
1646
		#region Connectivity Issues
1647
		#----------------------------------------------------------------------------------
1648
1649
		/**
1650
		 * Check if Freemius should be turned on for the current plugin install.
1651
		 *
1652
		 * Note:
1653
		 *  $this->_is_on is updated in has_api_connectivity()
1654
		 *
1655
		 * @author Vova Feldman (@svovaf)
1656
		 * @since  1.0.9
1657
		 *
1658
		 * @return bool
1659
		 */
1660
		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...
1661
			self::$_static_logger->entrance();
1662
1663
			if ( isset( $this->_is_on ) ) {
1664
				return $this->_is_on;
1665
			}
1666
1667
			// If already installed or pending then sure it's on :)
1668
			if ( $this->is_registered() || $this->is_pending_activation() ) {
1669
				$this->_is_on = true;
1670
1671
				return true;
1672
			}
1673
1674
			return false;
1675
		}
1676
1677
		/**
1678
		 * @author Vova Feldman (@svovaf)
1679
		 * @since  1.1.7.3
1680
		 *
1681
		 * @param bool $flush_if_no_connectivity
1682
		 *
1683
		 * @return bool
1684
		 */
1685
		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...
1686
			if ( ! isset( $this->_storage->connectivity_test ) ) {
1687
				// Connectivity test was never executed, or cache was cleared.
1688
				return true;
1689
			}
1690
1691
			if ( WP_FS__PING_API_ON_IP_OR_HOST_CHANGES ) {
1692
				if ( WP_FS__IS_HTTP_REQUEST ) {
1693
					if ( $_SERVER['HTTP_HOST'] != $this->_storage->connectivity_test['host'] ) {
1694
						// Domain changed.
1695
						return true;
1696
					}
1697
1698
					if ( WP_FS__REMOTE_ADDR != $this->_storage->connectivity_test['server_ip'] ) {
1699
						// Server IP changed.
1700
						return true;
1701
					}
1702
				}
1703
			}
1704
1705
			if ( $this->_storage->connectivity_test['is_connected'] &&
1706
			     $this->_storage->connectivity_test['is_active']
1707
			) {
1708
				// API connected and Freemius is active - no need to run connectivity check.
1709
				return false;
1710
			}
1711
1712
			if ( $flush_if_no_connectivity ) {
1713
				/**
1714
				 * If explicitly asked to flush when no connectivity - do it only
1715
				 * if at least 10 sec passed from the last API connectivity test.
1716
				 */
1717
				return ( isset( $this->_storage->connectivity_test['timestamp'] ) &&
1718
				         ( WP_FS__SCRIPT_START_TIME - $this->_storage->connectivity_test['timestamp'] ) > 10 );
1719
			}
1720
1721
			/**
1722
			 * @since 1.1.7 Don't check for connectivity on plugin downgrade.
1723
			 */
1724
			$version = $this->get_plugin_version();
1725
			if ( version_compare( $version, $this->_storage->connectivity_test['version'], '>' ) ) {
1726
				// If it's a plugin version upgrade and Freemius is off or no connectivity, run connectivity test.
1727
				return true;
1728
			}
1729
1730
			return false;
1731
		}
1732
1733
		/**
1734
		 * @author Vova Feldman (@svovaf)
1735
		 * @since  1.1.7.4
1736
		 *
1737
		 * @return object|false
1738
		 */
1739
		private function ping() {
1740
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) {
1741
				return false;
1742
			}
1743
1744
			$version = $this->get_plugin_version();
1745
1746
			$is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() );
1747
1748
			return $this->get_api_plugin_scope()->ping(
1749
				$this->get_anonymous_id(),
1750
				array(
1751
					'is_update' => json_encode( $is_update ),
1752
					'version'   => $version,
1753
					'sdk'       => $this->version,
1754
					'is_admin'  => json_encode( is_admin() ),
1755
					'is_ajax'   => json_encode( self::is_ajax() ),
1756
					'is_cron'   => json_encode( $this->is_cron() ),
1757
					'is_http'   => json_encode( WP_FS__IS_HTTP_REQUEST ),
1758
				)
1759
			);
1760
		}
1761
1762
		/**
1763
		 * Check if there's any connectivity issue to Freemius API.
1764
		 *
1765
		 * @author Vova Feldman (@svovaf)
1766
		 * @since  1.0.9
1767
		 *
1768
		 * @param bool $flush_if_no_connectivity
1769
		 *
1770
		 * @return bool
1771
		 */
1772
		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...
1773
			$this->_logger->entrance();
1774
1775
			if ( isset( $this->_has_api_connection ) && ( $this->_has_api_connection || ! $flush_if_no_connectivity ) ) {
1776
				return $this->_has_api_connection;
1777
			}
1778
1779
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY &&
1780
			     isset( $this->_storage->connectivity_test ) &&
1781
			     true === $this->_storage->connectivity_test['is_connected']
1782
			) {
1783
				unset( $this->_storage->connectivity_test );
1784
			}
1785
1786
			if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) {
1787
				$this->_has_api_connection = $this->_storage->connectivity_test['is_connected'];
1788
				/**
1789
				 * @since 1.1.6 During dev mode, if there's connectivity - turn Freemius on regardless the configuration.
1790
				 *
1791
				 * @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.
1792
				 */
1793
				$this->_is_on = $this->_storage->connectivity_test['is_active'] ||
1794
				                $this->is_premium() ||
1795
				                ( WP_FS__DEV_MODE && $this->_has_api_connection && ! WP_FS__SIMULATE_FREEMIUS_OFF );
1796
1797
				return $this->_has_api_connection;
1798
			}
1799
1800
			$pong         = $this->ping();
1801
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
1802
1803
			if ( ! $is_connected ) {
1804
				// API failure.
1805
				$this->_add_connectivity_issue_message( $pong );
1806
			}
1807
1808
			$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 1800 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...
1809
1810
			return $this->_has_api_connection;
1811
		}
1812
1813
		/**
1814
		 * @author Vova Feldman (@svovaf)
1815
		 * @since  1.1.7.4
1816
		 *
1817
		 * @param object $pong
1818
		 * @param bool   $is_connected
1819
		 */
1820
		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...
1821
			$this->_logger->entrance();
1822
1823
			$version = $this->get_plugin_version();
1824
1825
			if ( ! $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) {
1826
				$is_active = false;
1827
			} else {
1828
				$is_active = ( isset( $pong->is_active ) && true == $pong->is_active );
1829
			}
1830
1831
			$is_active = $this->apply_filters(
1832
				'is_on',
1833
				$is_active,
1834
				$this->is_plugin_update(),
1835
				$version
1836
			);
1837
1838
			$this->_storage->connectivity_test = array(
1839
				'is_connected' => $is_connected,
1840
				'host'         => $_SERVER['HTTP_HOST'],
1841
				'server_ip'    => WP_FS__REMOTE_ADDR,
1842
				'is_active'    => $is_active,
1843
				'timestamp'    => WP_FS__SCRIPT_START_TIME,
1844
				// Last version with connectivity attempt.
1845
				'version'      => $version,
1846
			);
1847
1848
			$this->_has_api_connection = $is_connected;
1849
			$this->_is_on              = $is_active || ( WP_FS__DEV_MODE && $is_connected && ! WP_FS__SIMULATE_FREEMIUS_OFF );
1850
		}
1851
1852
		/**
1853
		 * Force turning Freemius on.
1854
		 *
1855
		 * @author Vova Feldman (@svovaf)
1856
		 * @since  1.1.8.1
1857
		 *
1858
		 * @return bool TRUE if successfully turned on.
1859
		 */
1860
		private function turn_on() {
1861
			$this->_logger->entrance();
1862
1863
			if ( $this->is_on() || ! isset( $this->_storage->connectivity_test['is_active'] ) ) {
1864
				return false;
1865
			}
1866
1867
			$updated_connectivity              = $this->_storage->connectivity_test;
1868
			$updated_connectivity['is_active'] = true;
1869
			$updated_connectivity['timestamp'] = WP_FS__SCRIPT_START_TIME;
1870
			$this->_storage->connectivity_test = $updated_connectivity;
1871
1872
			$this->_is_on = true;
1873
1874
			return true;
1875
		}
1876
1877
		/**
1878
		 * Anonymous and unique site identifier (Hash).
1879
		 *
1880
		 * @author Vova Feldman (@svovaf)
1881
		 * @since  1.1.0
1882
		 *
1883
		 * @return string
1884
		 */
1885
		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...
1886
			$unique_id = self::$_accounts->get_option( 'unique_id' );
1887
1888
			if ( empty( $unique_id ) || ! is_string( $unique_id ) ) {
1889
				$key = get_site_url();
1890
1891
				// If localhost, assign microtime instead of domain.
1892
				if ( WP_FS__IS_LOCALHOST ||
1893
				     false !== strpos( $key, 'localhost' ) ||
1894
				     false === strpos( $key, '.' )
1895
				) {
1896
					$key = microtime();
1897
				}
1898
1899
				$unique_id = md5( $key );
1900
1901
				self::$_accounts->set_option( 'unique_id', $unique_id, true );
1902
			}
1903
1904
			$this->_logger->departure( $unique_id );
1905
1906
			return $unique_id;
1907
		}
1908
1909
		/**
1910
		 * @author Vova Feldman (@svovaf)
1911
		 * @since  1.1.7.4
1912
		 *
1913
		 * @return \WP_User
1914
		 */
1915
		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...
1916
			self::require_pluggable_essentials();
1917
1918
			return wp_get_current_user();
1919
		}
1920
1921
		/**
1922
		 * Generate API connectivity issue message.
1923
		 *
1924
		 * @author Vova Feldman (@svovaf)
1925
		 * @since  1.0.9
1926
		 *
1927
		 * @param mixed $api_result
1928
		 * @param bool  $is_first_failure
1929
		 */
1930
		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...
1931
			if ( ! $this->is_premium() && $this->_enable_anonymous ) {
1932
				// Don't add message if it's the free version and can run anonymously.
1933
				return;
1934
			}
1935
1936
			if ( ! function_exists( 'wp_nonce_url' ) ) {
1937
				require_once ABSPATH . 'wp-includes/functions.php';
1938
			}
1939
1940
			$current_user = self::_get_current_wp_user();
1941
//			$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...
1942
			$admin_email = $current_user->user_email;
1943
1944
			$message = false;
1945
			if ( is_object( $api_result ) &&
1946
			     isset( $api_result->error ) &&
1947
			     isset( $api_result->error->code )
1948
			) {
1949
				switch ( $api_result->error->code ) {
1950
					case 'curl_missing':
1951
						$missing_methods = '';
1952
						if ( is_array( $api_result->missing_methods ) &&
1953
						     ! empty( $api_result->missing_methods )
1954
						) {
1955
							foreach ( $api_result->missing_methods as $m ) {
1956
								if ( 'curl_version' === $m ) {
1957
									continue;
1958
								}
1959
1960
								if ( ! empty( $missing_methods ) ) {
1961
									$missing_methods .= ', ';
1962
								}
1963
1964
								$missing_methods .= sprintf( '<code>%s</code>', $m );
1965
							}
1966
1967
							if ( ! empty( $missing_methods ) ) {
1968
								$missing_methods = sprintf(
1969
									'<br><br><b>%s</b> %s',
1970
									$this->get_text( 'curl-disabled-methods' ),
1971
									$missing_methods
1972
								);
1973
							}
1974
						}
1975
1976
						$message = sprintf(
1977
							$this->get_text( 'x-requires-access-to-api' ) . ' ' .
1978
							$this->get_text( 'curl-missing-message' ) . ' ' .
1979
							$missing_methods .
1980
							' %s',
1981
							'<b>' . $this->get_plugin_name() . '</b>',
1982
							sprintf(
1983
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
1984
								sprintf(
1985
									'<a class="fs-resolve" data-type="curl" href="#"><b>%s</b></a>%s',
1986
									$this->get_text( 'curl-missing-no-clue-title' ),
1987
									' - ' . sprintf(
1988
										$this->get_text( 'curl-missing-no-clue-desc' ),
1989
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1990
									)
1991
								),
1992
								sprintf(
1993
									'<b>%s</b> - %s',
1994
									$this->get_text( 'sysadmin-title' ),
1995
									$this->get_text( 'curl-missing-sysadmin-desc' )
1996
								),
1997
								sprintf(
1998
									'<a href="%s"><b>%s</b></a>%s',
1999
									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 ),
2000
									$this->get_text( 'deactivate-plugin-title' ),
2001
									' - ' . $this->get_text( 'deactivate-plugin-desc' )
2002
								)
2003
							)
2004
						);
2005
						break;
2006
					case 'cloudflare_ddos_protection':
2007
						$message = sprintf(
2008
							$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2009
							$this->get_text( 'cloudflare-blocks-connection-message' ) . ' ' .
2010
							$this->get_text( 'happy-to-resolve-issue-asap' ) .
2011
							' %s',
2012
							'<b>' . $this->get_plugin_name() . '</b>',
2013
							sprintf(
2014
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2015
								sprintf(
2016
									'<a class="fs-resolve" data-type="cloudflare" href="#"><b>%s</b></a>%s',
2017
									$this->get_text( 'fix-issue-title' ),
2018
									' - ' . sprintf(
2019
										$this->get_text( 'fix-issue-desc' ),
2020
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2021
									)
2022
								),
2023
								sprintf(
2024
									'<a href="%s" target="_blank"><b>%s</b></a>%s',
2025
									sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
2026
									$this->get_text( 'install-previous-title' ),
2027
									' - ' . $this->get_text( 'install-previous-desc' )
2028
								),
2029
								sprintf(
2030
									'<a href="%s"><b>%s</b></a>%s',
2031
									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 ),
2032
									$this->get_text( 'deactivate-plugin-title' ),
2033
									' - ' . $this->get_text( 'deactivate-plugin-desc' )
2034
								)
2035
							)
2036
						);
2037
						break;
2038
					case 'squid_cache_block':
2039
						$message = sprintf(
2040
							$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2041
							$this->get_text( 'squid-blocks-connection-message' ) .
2042
							' %s',
2043
							'<b>' . $this->get_plugin_name() . '</b>',
2044
							sprintf(
2045
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2046
								sprintf(
2047
									'<a class="fs-resolve" data-type="squid" href="#"><b>%s</b></a>%s',
2048
									$this->get_text( 'squid-no-clue-title' ),
2049
									' - ' . sprintf(
2050
										$this->get_text( 'squid-no-clue-desc' ),
2051
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2052
									)
2053
								),
2054
								sprintf(
2055
									'<b>%s</b> - %s',
2056
									$this->get_text( 'sysadmin-title' ),
2057
									sprintf(
2058
										$this->get_text( 'squid-sysadmin-desc' ),
2059
										// We use a filter since the plugin might require additional API connectivity.
2060
										'<b>' . implode( ', ', $this->apply_filters( 'api_domains', array( 'api.freemius.com' ) ) ) . '</b>' )
2061
								),
2062
								sprintf(
2063
									'<a href="%s"><b>%s</b></a>%s',
2064
									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 ),
2065
									$this->get_text( 'deactivate-plugin-title' ),
2066
									' - ' . $this->get_text( 'deactivate-plugin-desc' )
2067
								)
2068
							)
2069
						);
2070
						break;
2071
//					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...
2072
//						$message = $this->get_text( 'connectivity-test-fails-message' );
2073
//						break;
2074
				}
2075
			}
2076
2077
			$message_id = 'failed_connect_api';
2078
			$type       = 'error';
2079
2080
			if ( false === $message ) {
2081
				if ( $is_first_failure ) {
2082
					// First attempt failed.
2083
					$message = sprintf(
2084
						$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2085
						$this->get_text( 'connectivity-test-fails-message' ) . ' ' .
2086
						$this->get_text( 'connectivity-test-maybe-temporary' ) . '<br><br>' .
2087
						'%s',
2088
						'<b>' . $this->get_plugin_name() . '</b>',
2089
						sprintf(
2090
							'<div id="fs_firewall_issue_options">%s %s</div>',
2091
							sprintf(
2092
								'<a  class="button button-primary fs-resolve" data-type="retry_ping" href="#">%s</a>',
2093
								$this->get_text( 'yes-do-your-thing' )
2094
							),
2095
							sprintf(
2096
								'<a href="%s" class="button">%s</a>',
2097
								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 ),
2098
								$this->get_text( 'no-deactivate' )
2099
							)
2100
						)
2101
					);
2102
2103
					$message_id = 'failed_connect_api_first';
2104
					$type       = 'promotion';
2105
				} else {
2106
					// Second connectivity attempt failed.
2107
					$message = sprintf(
2108
						$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2109
						$this->get_text( 'connectivity-test-fails-message' ) . ' ' .
2110
						$this->get_text( 'happy-to-resolve-issue-asap' ) .
2111
						' %s',
2112
						'<b>' . $this->get_plugin_name() . '</b>',
2113
						sprintf(
2114
							'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2115
							sprintf(
2116
								'<a class="fs-resolve" data-type="general" href="#"><b>%s</b></a>%s',
2117
								$this->get_text( 'fix-issue-title' ),
2118
								' - ' . sprintf(
2119
									$this->get_text( 'fix-issue-desc' ),
2120
									'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2121
								)
2122
							),
2123
							sprintf(
2124
								'<a href="%s" target="_blank"><b>%s</b></a>%s',
2125
								sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
2126
								$this->get_text( 'install-previous-title' ),
2127
								' - ' . $this->get_text( 'install-previous-desc' )
2128
							),
2129
							sprintf(
2130
								'<a href="%s"><b>%s</b></a>%s',
2131
								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 ),
2132
								$this->get_text( 'deactivate-plugin-title' ),
2133
								' - ' . $this->get_text( 'deactivate-plugin-desc' )
2134
							)
2135
						)
2136
					);
2137
				}
2138
			}
2139
2140
			$this->_admin_notices->add_sticky(
2141
				$message,
2142
				$message_id,
2143
				$this->get_text( 'oops' ) . '...',
2144
				$type
2145
			);
2146
		}
2147
2148
		/**
2149
		 * Handle user request to resolve connectivity issue.
2150
		 * This method will send an email to Freemius API technical staff for resolution.
2151
		 * The email will contain server's info and installed plugins (might be caching issue).
2152
		 *
2153
		 * @author Vova Feldman (@svovaf)
2154
		 * @since  1.0.9
2155
		 */
2156
		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...
2157
			$this->_admin_notices->remove_sticky( 'failed_connect_api' );
2158
2159
			$pong = $this->ping();
2160
2161
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2162
2163
			if ( $is_connected ) {
2164
				$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 2159 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...
2165
2166
				echo $this->get_after_plugin_activation_redirect_url();
2167
				exit;
2168
			}
2169
2170
			$current_user = self::_get_current_wp_user();
2171
			$admin_email  = $current_user->user_email;
2172
2173
			$error_type = fs_request_get( 'error_type', 'general' );
2174
2175
			switch ( $error_type ) {
2176
				case 'squid':
2177
					$title = 'Squid ACL Blocking Issue';
2178
					break;
2179
				case 'cloudflare':
2180
					$title = 'CloudFlare Blocking Issue';
2181
					break;
2182
				default:
2183
					$title = 'API Connectivity Issue';
2184
					break;
2185
			}
2186
2187
			$custom_email_sections = array();
2188
2189
			// Add 'API Error' custom email section.
2190
			$custom_email_sections['api_error'] = array(
2191
				'title' => 'API Error',
2192
				'rows'  => array(
2193
					'ping' => array(
2194
						'API Error',
2195
						is_string( $pong ) ? htmlentities( $pong ) : json_encode( $pong )
2196
					),
2197
				)
2198
			);
2199
2200
			// Send email with technical details to resolve API connectivity issues.
2201
			$this->send_email(
2202
				'[email protected]',                              // recipient
2203
				$title . ' [' . $this->get_plugin_name() . ']',  // subject
2204
				$custom_email_sections,
2205
				array( "Reply-To: $admin_email <$admin_email>" ) // headers
2206
			);
2207
2208
			$this->_admin_notices->add_sticky(
2209
				sprintf(
2210
					$this->get_text( 'fix-request-sent-message' ),
2211
					'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2212
				),
2213
				'server_details_sent'
2214
			);
2215
2216
			// Action was taken, tell that API connectivity troubleshooting should be off now.
2217
2218
			echo "1";
2219
			exit;
2220
		}
2221
2222
		/**
2223
		 * Handle connectivity test retry approved by the user.
2224
		 *
2225
		 * @author Vova Feldman (@svovaf)
2226
		 * @since  1.1.7.4
2227
		 */
2228
		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...
2229
			$this->_admin_notices->remove_sticky( 'failed_connect_api_first' );
2230
2231
			$pong = $this->ping();
2232
2233
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2234
2235
			if ( $is_connected ) {
2236
				$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 2231 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...
2237
2238
				echo $this->get_after_plugin_activation_redirect_url();
2239
			} else {
2240
				// Add connectivity issue message after 2nd failed attempt.
2241
				$this->_add_connectivity_issue_message( $pong, false );
2242
2243
				echo "1";
2244
			}
2245
2246
			exit;
2247
		}
2248
2249
		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...
2250
			$params = array();
2251
			fs_require_once_template( 'firewall-issues-js.php', $params );
2252
		}
2253
2254
		#endregion
2255
2256
		#----------------------------------------------------------------------------------
2257
		#region Email
2258
		#----------------------------------------------------------------------------------
2259
2260
		/**
2261
		 * Generates and sends an HTML email with customizable sections.
2262
		 *
2263
		 * @author Leo Fajardo (@leorw)
2264
		 * @since  1.1.2
2265
		 *
2266
		 * @param string $to_address
2267
		 * @param string $subject
2268
		 * @param array  $sections
2269
		 * @param array  $headers
2270
		 *
2271
		 * @return bool Whether the email contents were sent successfully.
2272
		 */
2273
		private function send_email(
2274
			$to_address,
2275
			$subject,
2276
			$sections = array(),
2277
			$headers = array()
2278
		) {
2279
			$default_sections = $this->get_email_sections();
2280
2281
			// Insert new sections or replace the default email sections.
2282
			if ( is_array( $sections ) && ! empty( $sections ) ) {
2283
				foreach ( $sections as $section_id => $custom_section ) {
2284
					if ( ! isset( $default_sections[ $section_id ] ) ) {
2285
						// If the section does not exist, add it.
2286
						$default_sections[ $section_id ] = $custom_section;
2287
					} else {
2288
						// If the section already exists, override it.
2289
						$current_section = $default_sections[ $section_id ];
2290
2291
						// Replace the current section's title if a custom section title exists.
2292
						if ( isset( $custom_section['title'] ) ) {
2293
							$current_section['title'] = $custom_section['title'];
2294
						}
2295
2296
						// Insert new rows under the current section or replace the default rows.
2297
						if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) {
2298
							foreach ( $custom_section['rows'] as $row_id => $row ) {
2299
								$current_section['rows'][ $row_id ] = $row;
2300
							}
2301
						}
2302
2303
						$default_sections[ $section_id ] = $current_section;
2304
					}
2305
				}
2306
			}
2307
2308
			$vars    = array( 'sections' => $default_sections );
2309
			$message = fs_get_template( 'email.php', $vars );
2310
2311
			// Set the type of email to HTML.
2312
			$headers[] = 'Content-type: text/html; charset=UTF-8';
2313
2314
			$header_string = implode( "\r\n", $headers );
2315
2316
			return wp_mail(
2317
				$to_address,
2318
				$subject,
2319
				$message,
2320
				$header_string
2321
			);
2322
		}
2323
2324
		/**
2325
		 * Generates the data for the sections of the email content.
2326
		 *
2327
		 * @author Leo Fajardo (@leorw)
2328
		 * @since  1.1.2
2329
		 *
2330
		 * @return array
2331
		 */
2332
		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...
2333
			// Retrieve the current user's information so that we can get the user's email, first name, and last name below.
2334
			$current_user = self::_get_current_wp_user();
2335
2336
			// Retrieve the cURL version information so that we can get the version number below.
2337
			$curl_version_information = curl_version();
2338
2339
			$active_plugin = self::get_active_plugins();
2340
2341
			// Generate the list of active plugins separated by new line. 
2342
			$active_plugin_string = '';
2343
			foreach ( $active_plugin as $plugin ) {
2344
				$active_plugin_string .= sprintf(
2345
					'<a href="%s">%s</a> [v%s]<br>',
2346
					$plugin['PluginURI'],
2347
					$plugin['Name'],
2348
					$plugin['Version']
2349
				);
2350
			}
2351
2352
			$server_ip = WP_FS__REMOTE_ADDR;
2353
2354
			// Add PHP info for deeper investigation.
2355
			ob_start();
2356
			phpinfo();
2357
			$php_info = ob_get_clean();
2358
2359
			$api_domain = substr( FS_API__ADDRESS, strpos( FS_API__ADDRESS, ':' ) + 3 );
2360
2361
			// Generate the default email sections.
2362
			$sections = array(
2363
				'sdk'      => array(
2364
					'title' => 'SDK',
2365
					'rows'  => array(
2366
						'fs_version'   => array( 'FS Version', $this->version ),
2367
						'curl_version' => array( 'cURL Version', $curl_version_information['version'] )
2368
					)
2369
				),
2370
				'plugin'   => array(
2371
					'title' => 'Plugin',
2372
					'rows'  => array(
2373
						'name'    => array( 'Name', $this->get_plugin_name() ),
2374
						'version' => array( 'Version', $this->get_plugin_version() )
2375
					)
2376
				),
2377
				'api'      => array(
2378
					'title' => 'API Subdomain',
2379
					'rows'  => array(
2380
						'dns' => array( 'DNS_CNAME', var_export( @dns_get_record( $api_domain, DNS_CNAME ), true ) ),
2381
						'ip'  => array( 'IP', @gethostbyname( $api_domain ) ),
2382
					),
2383
				),
2384
				'site'     => array(
2385
					'title' => 'Site',
2386
					'rows'  => array(
2387
						'unique_id'   => array( 'Unique ID', $this->get_anonymous_id() ),
2388
						'address'     => array( 'Address', site_url() ),
2389
						'host'        => array(
2390
							'HTTP_HOST',
2391
							( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' )
2392
						),
2393
						'hosting'     => array(
2394
							'Hosting Company' => fs_request_has( 'hosting_company' ) ?
2395
								fs_request_get( 'hosting_company' ) :
2396
								'Unknown',
2397
						),
2398
						'server_addr' => array(
2399
							'SERVER_ADDR',
2400
							'<a href="http://www.projecthoneypot.org/ip_' . $server_ip . '">' . $server_ip . '</a>'
2401
						)
2402
					)
2403
				),
2404
				'user'     => array(
2405
					'title' => 'User',
2406
					'rows'  => array(
2407
						'email' => array( 'Email', $current_user->user_email ),
2408
						'first' => array( 'First', $current_user->user_firstname ),
2409
						'last'  => array( 'Last', $current_user->user_lastname )
2410
					)
2411
				),
2412
				'plugins'  => array(
2413
					'title' => 'Plugins',
2414
					'rows'  => array(
2415
						'active_plugins' => array( 'Active Plugins', $active_plugin_string )
2416
					)
2417
				),
2418
				'php_info' => array(
2419
					'title' => 'PHP Info',
2420
					'rows'  => array(
2421
						'info' => array( $php_info )
2422
					),
2423
				)
2424
			);
2425
2426
			// Allow the sections to be modified by other code.
2427
			$sections = $this->apply_filters( 'email_template_sections', $sections );
2428
2429
			return $sections;
2430
		}
2431
2432
		#endregion
2433
2434
		#----------------------------------------------------------------------------------
2435
		#region Initialization
2436
		#----------------------------------------------------------------------------------
2437
2438
		/**
2439
		 * Init plugin's Freemius instance.
2440
		 *
2441
		 * @author Vova Feldman (@svovaf)
2442
		 * @since  1.0.1
2443
		 *
2444
		 * @param number $id
2445
		 * @param string $public_key
2446
		 * @param bool   $is_live
2447
		 * @param bool   $is_premium
2448
		 */
2449
		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...
2450
			$this->_logger->entrance();
2451
2452
			$this->dynamic_init( array(
2453
				'id'         => $id,
2454
				'public_key' => $public_key,
2455
				'is_live'    => $is_live,
2456
				'is_premium' => $is_premium,
2457
			) );
2458
		}
2459
2460
		/**
2461
		 * Dynamic initiator, originally created to support initiation
2462
		 * with parent_id for add-ons.
2463
		 *
2464
		 * @author Vova Feldman (@svovaf)
2465
		 * @since  1.0.6
2466
		 *
2467
		 * @param array $plugin_info
2468
		 *
2469
		 * @throws Freemius_Exception
2470
		 */
2471
		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...
2472
			$this->_logger->entrance();
2473
2474
			$this->parse_settings( $plugin_info );
2475
2476
			if ( $this->should_stop_execution() ) {
2477
				return;
2478
			}
2479
2480
			if ( ! $this->is_registered() ) {
2481
				if ( $this->is_anonymous() ) {
2482
					// If user skipped, no need to test connectivity.
2483
					$this->_has_api_connection = true;
2484
					$this->_is_on              = true;
2485
				} else {
2486
					if ( ! $this->has_api_connectivity() ) {
2487
						if ( $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) ||
2488
						     $this->_admin_notices->has_sticky( 'failed_connect_api' )
2489
						) {
2490
							if ( ! $this->_enable_anonymous || $this->is_premium() ) {
2491
								// If anonymous mode is disabled, add firewall admin-notice message.
2492
								add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) );
2493
2494
								$this->add_ajax_action( 'resolve_firewall_issues', array(
2495
									&$this,
2496
									'_email_about_firewall_issue'
2497
								) );
2498
2499
								$this->add_ajax_action( 'retry_connectivity_test', array(
2500
									&$this,
2501
									'_retry_connectivity_test'
2502
								) );
2503
							}
2504
						}
2505
2506
						return;
2507
					} else {
2508
						$this->_admin_notices->remove_sticky( array(
2509
							'failed_connect_api_first',
2510
							'failed_connect_api',
2511
						) );
2512
2513
						if ( $this->_anonymous_mode ) {
2514
							// Simulate anonymous mode.
2515
							$this->_is_anonymous = true;
2516
						}
2517
					}
2518
				}
2519
2520
				// Check if Freemius is on for the current plugin.
2521
				// This MUST be executed after all the plugin variables has been loaded.
2522
				if ( ! $this->is_on() ) {
2523
					return;
2524
				}
2525
			}
2526
2527
			if ( $this->has_api_connectivity() ) {
2528
				if ( $this->is_cron() ) {
2529
					$this->hook_callback_to_sync_cron();
2530
				} else if ( $this->is_user_in_admin() ) {
2531
					/**
2532
					 * Schedule daily data sync cron if:
2533
					 *
2534
					 *  1. User opted-in (for tracking).
2535
					 *  2. If skipped, but later upgraded (opted-in via upgrade).
2536
					 *
2537
					 * @author Vova Feldman (@svovaf)
2538
					 * @since  1.1.7.3
2539
					 *
2540
					 */
2541
					if ( $this->is_registered() ) {
2542
						if ( ! $this->is_sync_cron_on() && $this->is_tracking_allowed() ) {
2543
							$this->schedule_sync_cron();
2544
						}
2545
					}
2546
2547
					/**
2548
					 * Check if requested for manual blocking background sync.
2549
					 */
2550
					if ( fs_request_has( 'background_sync' ) ) {
2551
						$this->run_manual_sync();
2552
					}
2553
				}
2554
			}
2555
2556
			if ( $this->is_registered() ) {
2557
				$this->hook_callback_to_install_sync();
2558
			}
2559
2560
			if ( $this->is_addon() ) {
2561
				if ( $this->is_parent_plugin_installed() ) {
2562
					// Link to parent FS.
2563
					$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...
2564
2565
					// Get parent plugin reference.
2566
					$this->_parent_plugin = $this->_parent->get_plugin();
2567
				}
2568
			}
2569
2570
			if ( $this->is_user_in_admin() ) {
2571
				if ( $this->is_plugins_page() ) {
2572
					$this->hook_plugin_action_links();
2573
				}
2574
2575
				if ( $this->is_addon() ) {
2576
					if ( ! $this->is_parent_plugin_installed() ) {
2577
						$parent_name = $this->get_option( $plugin_info, 'parent_name', null );
2578
2579
						if ( isset( $plugin_info['parent'] ) ) {
2580
							$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...
2581
						}
2582
2583
						$this->_admin_notices->add(
2584
							( ! empty( $parent_name ) ?
2585
								sprintf( $this->get_text( 'addon-x-cannot-run-without-y' ), $this->get_plugin_name(), $parent_name ) :
2586
								sprintf( $this->get_text( 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() )
2587
							),
2588
							$this->get_text( 'oops' ) . '...',
2589
							'error'
2590
						);
2591
2592
						return;
2593
					} else {
2594
						if ( $this->_parent->is_registered() && ! $this->is_registered() ) {
2595
							// If parent plugin activated, automatically install add-on for the user.
2596
							$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...
2597
						}
2598
2599
						// @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic.
2600
						if ( $this->is_premium() ) {
2601
							// Remove add-on download admin-notice.
2602
							$this->_parent->_admin_notices->remove_sticky( array(
2603
								'addon_plan_upgraded_' . $this->_slug,
2604
								'no_addon_license_' . $this->_slug,
2605
							) );
2606
						}
2607
2608
						$this->deactivate_premium_only_addon_without_license();
2609
					}
2610
				} else {
2611
					if ( $this->has_addons() &&
2612
					     'plugin-information' === fs_request_get( 'tab', false ) &&
2613
					     $this->get_id() == fs_request_get( 'parent_plugin_id', false )
2614
					) {
2615
						require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php';
2616
2617
						new FS_Plugin_Info_Dialog( $this );
2618
					}
2619
				}
2620
2621
				add_action( 'admin_init', array( &$this, '_admin_init_action' ) );
2622
2623
//				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...
2624
//				     $this->is_anonymous() ||
2625
//				     $this->is_pending_activation()
2626
//				) {
2627
//					$this->_init_admin();
2628
//				}
2629
			}
2630
2631
			/**
2632
			 * Should be called outside `$this->is_user_in_admin()` scope
2633
			 * because the updater has some logic that needs to be executed
2634
			 * during AJAX calls.
2635
			 *
2636
			 * Currently we need to hook to the `http_request_host_is_external` filter.
2637
			 * In the future, there might be additional logic added.
2638
			 *
2639
			 * @author Vova Feldman
2640
			 * @since  1.2.1.6
2641
			 */
2642
			if ( $this->is_premium() && $this->has_release_on_freemius() ) {
2643
				new FS_Plugin_Updater( $this );
2644
			}
2645
2646
			$this->do_action( 'initiated' );
2647
2648
			if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) {
2649
				if ( isset( $this->_storage->prev_is_premium ) ) {
2650
					$this->apply_filters(
2651
						'after_code_type_change',
2652
						// New code type.
2653
						$this->_plugin->is_premium
2654
					);
2655
				} else {
2656
					// Set for code type for the first time.
2657
					$this->_storage->prev_is_premium = $this->_plugin->is_premium;
2658
				}
2659
			}
2660
2661
			if ( ! $this->is_addon() ) {
2662
				if ( $this->is_registered() ) {
2663
					// Fix for upgrade from versions < 1.0.9.
2664
					if ( ! isset( $this->_storage->activation_timestamp ) ) {
2665
						$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
2666
					}
2667
2668
					$this->do_action( 'after_init_plugin_registered' );
2669
				} else if ( $this->is_anonymous() ) {
2670
					$this->do_action( 'after_init_plugin_anonymous' );
2671
				} else if ( $this->is_pending_activation() ) {
2672
					$this->do_action( 'after_init_plugin_pending_activations' );
2673
				}
2674
			} else {
2675
				if ( $this->is_registered() ) {
2676
					$this->do_action( 'after_init_addon_registered' );
2677
				} else if ( $this->is_anonymous() ) {
2678
					$this->do_action( 'after_init_addon_anonymous' );
2679
				} else if ( $this->is_pending_activation() ) {
2680
					$this->do_action( 'after_init_addon_pending_activations' );
2681
				}
2682
			}
2683
		}
2684
2685
		/**
2686
		 * @author Leo Fajardo (@leorw)
2687
		 *
2688
		 * @since  1.2.1.5
2689
		 */
2690
		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...
2691
			$this->_logger->entrance();
2692
2693
			$this->check_ajax_referer( 'stop_tracking' );
2694
2695
			$result = $this->stop_tracking();
2696
2697
			if ( true === $result ) {
2698
				self::shoot_ajax_success();
2699
			}
2700
2701
			$this->_logger->api_error( $result );
2702
2703
			self::shoot_ajax_failure(
2704
				$this->get_text( 'unexpected-api-error' ) .
2705
				( $this->is_api_error( $result ) && isset( $result->error ) ?
2706
					$result->error->message :
2707
					var_export( $result, true ) )
2708
			);
2709
		}
2710
2711
		/**
2712
		 * @author Leo Fajardo (@leorw)
2713
		 * @since  1.2.1.5
2714
		 */
2715
		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...
2716
			$this->_logger->entrance();
2717
2718
			$this->check_ajax_referer( 'allow_tracking' );
2719
2720
			$result = $this->allow_tracking();
2721
2722
			if ( true === $result ) {
2723
				self::shoot_ajax_success();
2724
			}
2725
2726
			$this->_logger->api_error( $result );
2727
2728
			self::shoot_ajax_failure(
2729
				$this->get_text( 'unexpected-api-error' ) .
2730
				( $this->is_api_error( $result ) && isset( $result->error ) ?
2731
					$result->error->message :
2732
					var_export( $result, true ) )
2733
			);
2734
		}
2735
2736
		/**
2737
		 * Opt-out from usage tracking.
2738
		 *
2739
		 * Note: This will not delete the account information but will stop all tracking.
2740
		 *
2741
		 * Returns:
2742
		 *  1. FALSE  - If the user never opted-in.
2743
		 *  2. TRUE   - If successfully opted-out.
2744
		 *  3. object - API result on failure.
2745
		 *
2746
		 * @author Leo Fajardo (@leorw)
2747
		 * @since  1.2.1.5
2748
		 *
2749
		 * @return bool|object
2750
		 */
2751
		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...
2752
			$this->_logger->entrance();
2753
2754
			if ( ! $this->is_registered() ) {
2755
				// User never opted-in.
2756
				return false;
2757
			}
2758
2759
			if ( $this->is_tracking_prohibited() ) {
2760
				// Already disconnected.
2761
				return true;
2762
			}
2763
2764
			// Send update to FS.
2765
			$result = $this->get_api_site_scope()->call( '/?fields=is_disconnected', 'put', array(
2766
				'is_disconnected' => true
2767
			) );
2768
2769
			if ( ! $this->is_api_result_entity( $result ) ||
2770
			     ! isset( $result->is_disconnected ) ||
2771
			     ! $result->is_disconnected
2772
			) {
2773
				$this->_logger->api_error( $result );
2774
2775
				return $result;
2776
			}
2777
2778
			$this->_site->is_disconnected = $result->is_disconnected;
2779
			$this->_store_site();
2780
2781
			$this->clear_sync_cron();
2782
2783
			// Successfully disconnected.
2784
			return true;
2785
		}
2786
2787
		/**
2788
		 * Opt-in back into usage tracking.
2789
		 *
2790
		 * Note: This will only work if the user opted-in previously.
2791
		 *
2792
		 * Returns:
2793
		 *  1. FALSE  - If the user never opted-in.
2794
		 *  2. TRUE   - If successfully opted-in back to usage tracking.
2795
		 *  3. object - API result on failure.
2796
		 *
2797
		 * @author Leo Fajardo (@leorw)
2798
		 * @since  1.2.1.5
2799
		 *
2800
		 * @return bool|object
2801
		 */
2802
		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...
2803
			$this->_logger->entrance();
2804
2805
			if ( ! $this->is_registered() ) {
2806
				// User never opted-in.
2807
				return false;
2808
			}
2809
2810
			if ( $this->is_tracking_allowed() ) {
2811
				// Tracking already allowed.
2812
				return true;
2813
			}
2814
2815
			$result = $this->get_api_site_scope()->call( '/?is_disconnected', 'put', array(
2816
				'is_disconnected' => false
2817
			) );
2818
2819
			if ( ! $this->is_api_result_entity( $result ) ||
2820
			     ! isset( $result->is_disconnected ) ||
2821
			     $result->is_disconnected
2822
			) {
2823
				$this->_logger->api_error( $result );
2824
2825
				return $result;
2826
			}
2827
2828
			$this->_site->is_disconnected = $result->is_disconnected;
2829
			$this->_store_site();
2830
2831
			$this->schedule_sync_cron();
2832
2833
			// Successfully reconnected.
2834
			return true;
2835
		}
2836
2837
		/**
2838
		 * If user opted-in and later disabled usage-tracking,
2839
		 * re-allow tracking for licensing and updates.
2840
		 *
2841
		 * @author Leo Fajardo (@leorw)
2842
		 *
2843
		 * @since  1.2.1.5
2844
		 */
2845
		private function reconnect_locally() {
2846
			$this->_logger->entrance();
2847
2848
			if ( $this->is_tracking_prohibited() &&
2849
			     $this->is_registered()
2850
			) {
2851
				$this->_site->is_disconnected = false;
2852
				$this->_store_site();
2853
			}
2854
		}
2855
2856
		/**
2857
		 * Parse plugin's settings (as defined by the plugin dev).
2858
		 *
2859
		 * @author Vova Feldman (@svovaf)
2860
		 * @since  1.1.7.3
2861
		 *
2862
		 * @param array $plugin_info
2863
		 *
2864
		 * @throws \Freemius_Exception
2865
		 */
2866
		private function parse_settings( &$plugin_info ) {
2867
			$this->_logger->entrance();
2868
2869
			$id          = $this->get_numeric_option( $plugin_info, 'id', false );
2870
			$public_key  = $this->get_option( $plugin_info, 'public_key', false );
2871
			$secret_key  = $this->get_option( $plugin_info, 'secret_key', null );
2872
			$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...
2873
			$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...
2874
2875
			/**
2876
			 * @author Vova Feldman (@svovaf)
2877
			 * @since  1.1.9 Try to pull secret key from external config.
2878
			 */
2879
			if ( is_null( $secret_key ) && defined( "WP_FS__{$this->_slug}_SECRET_KEY" ) ) {
2880
				$secret_key = constant( "WP_FS__{$this->_slug}_SECRET_KEY" );
2881
			}
2882
2883
			if ( isset( $plugin_info['parent'] ) ) {
2884
				$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...
2885
//				$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...
2886
//				$parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null );
2887
//				$parent_name = $this->get_option( $plugin_info['parent'], 'name', null );
2888
			}
2889
2890
			if ( false === $id ) {
2891
				throw new Freemius_Exception( 'Plugin id parameter is not set.' );
0 ignored issues
show
Documentation introduced by
'Plugin id parameter is not set.' 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...
2892
			}
2893
			if ( false === $public_key ) {
2894
				throw new Freemius_Exception( 'Plugin public_key parameter is not set.' );
0 ignored issues
show
Documentation introduced by
'Plugin public_key parameter is not set.' 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...
2895
			}
2896
2897
			$plugin = ( $this->_plugin instanceof FS_Plugin ) ?
2898
				$this->_plugin :
2899
				new FS_Plugin();
2900
2901
			$plugin->update( array(
2902
				'id'               => $id,
2903
				'public_key'       => $public_key,
2904
				'slug'             => $this->_slug,
2905
				'parent_plugin_id' => $parent_id,
2906
				'version'          => $this->get_plugin_version(),
2907
				'title'            => $this->get_plugin_name(),
2908
				'file'             => $this->_plugin_basename,
2909
				'is_premium'       => $this->get_bool_option( $plugin_info, 'is_premium', true ),
2910
				'is_live'          => $this->get_bool_option( $plugin_info, 'is_live', true ),
2911
//				'secret_key' => $secret_key,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
2912
			) );
2913
2914
			if ( $plugin->is_updated() ) {
2915
				// Update plugin details.
2916
				$this->_plugin = FS_Plugin_Manager::instance( $this->_slug )->store( $plugin );
0 ignored issues
show
Documentation Bug introduced by
It seems like \FS_Plugin_Manager::inst...>_slug)->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...
2917
			}
2918
			// Set the secret key after storing the plugin, we don't want to store the key in the storage.
2919
			$this->_plugin->secret_key = $secret_key;
2920
2921
			if ( ! isset( $plugin_info['menu'] ) ) {
2922
				$plugin_info['menu'] = array();
2923
2924
				if ( ! empty( $this->_storage->sdk_last_version ) &&
2925
				     version_compare( $this->_storage->sdk_last_version, '1.1.2', '<=' )
2926
				) {
2927
					// Backward compatibility to 1.1.2
2928
					$plugin_info['menu']['slug'] = isset( $plugin_info['menu_slug'] ) ?
2929
						$plugin_info['menu_slug'] :
2930
						$this->_slug;
2931
				}
2932
			}
2933
2934
			$this->_menu = FS_Admin_Menu_Manager::instance( $this->_slug );
2935
			$this->_menu->init( $plugin_info['menu'], $this->is_addon() );
2936
2937
			$this->_has_addons          = $this->get_bool_option( $plugin_info, 'has_addons', false );
2938
			$this->_has_paid_plans      = $this->get_bool_option( $plugin_info, 'has_paid_plans', true );
2939
			$this->_has_premium_version = $this->get_bool_option( $plugin_info, 'has_premium_version', $this->_has_paid_plans );
2940
			$this->_ignore_pending_mode = $this->get_bool_option( $plugin_info, 'ignore_pending_mode', false );
2941
			$this->_is_org_compliant    = $this->get_bool_option( $plugin_info, 'is_org_compliant', true );
2942
			$this->_is_premium_only     = $this->get_bool_option( $plugin_info, 'is_premium_only', false );
2943
			if ( $this->_is_premium_only ) {
2944
				// If premium only plugin, disable anonymous mode.
2945
				$this->_enable_anonymous = false;
2946
				$this->_anonymous_mode   = false;
2947
			} else {
2948
				$this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true );
2949
				$this->_anonymous_mode   = $this->get_bool_option( $plugin_info, 'anonymous_mode', false );
2950
			}
2951
			$this->_permissions = $this->get_option( $plugin_info, 'permissions', array() );
2952
2953
			if ( ! empty( $plugin_info['trial'] ) ) {
2954
				$this->_trial_days = $this->get_numeric_option(
2955
					$plugin_info['trial'],
2956
					'days',
2957
					// Default to 0 - trial without days specification.
2958
					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...
2959
				);
2960
2961
				$this->_is_trial_require_payment = $this->get_bool_option( $plugin_info['trial'], 'is_require_payment', false );
2962
			}
2963
		}
2964
2965
		/**
2966
		 * @param string[] $options
2967
		 * @param string   $key
2968
		 * @param mixed    $default
2969
		 *
2970
		 * @return bool
2971
		 */
2972
		private function get_option( &$options, $key, $default = false ) {
2973
			return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
2974
		}
2975
2976
		private function get_bool_option( &$options, $key, $default = false ) {
2977
			return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
2978
		}
2979
2980
		private function get_numeric_option( &$options, $key, $default = false ) {
2981
			return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default;
2982
		}
2983
2984
		/**
2985
		 * Gate keeper.
2986
		 *
2987
		 * @author Vova Feldman (@svovaf)
2988
		 * @since  1.1.7.3
2989
		 *
2990
		 * @return bool
2991
		 */
2992
		private function should_stop_execution() {
2993
			if ( empty( $this->_storage->was_plugin_loaded ) ) {
2994
				/**
2995
				 * Don't execute Freemius until plugin was fully loaded at least once,
2996
				 * to give the opportunity for the activation hook to run before pinging
2997
				 * the API for connectivity test. This logic is relevant for the
2998
				 * identification of new plugin install vs. plugin update.
2999
				 *
3000
				 * @author Vova Feldman (@svovaf)
3001
				 * @since  1.1.9
3002
				 */
3003
				return true;
3004
			}
3005
3006
			if ( $this->is_activation_mode() ) {
3007
				if ( ! is_admin() ) {
3008
					/**
3009
					 * If in activation mode, don't execute Freemius outside of the
3010
					 * admin dashboard.
3011
					 *
3012
					 * @author Vova Feldman (@svovaf)
3013
					 * @since  1.1.7.3
3014
					 */
3015
					return true;
3016
				}
3017
3018
				if ( ! WP_FS__IS_HTTP_REQUEST ) {
3019
					/**
3020
					 * If in activation and executed without HTTP context (e.g. CLI, Cronjob),
3021
					 * then don't start Freemius.
3022
					 *
3023
					 * @author Vova Feldman (@svovaf)
3024
					 * @since  1.1.6.3
3025
					 *
3026
					 * @link   https://wordpress.org/support/topic/errors-in-the-freemius-class-when-running-in-wordpress-in-cli
3027
					 */
3028
					return true;
3029
				}
3030
3031
				if ( $this->is_cron() ) {
3032
					/**
3033
					 * If in activation mode, don't execute Freemius during wp crons
3034
					 * (wp crons have HTTP context - called as HTTP request).
3035
					 *
3036
					 * @author Vova Feldman (@svovaf)
3037
					 * @since  1.1.7.3
3038
					 */
3039
					return true;
3040
				}
3041
3042
				if ( self::is_ajax() &&
3043
				     ! $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) &&
3044
				     ! $this->_admin_notices->has_sticky( 'failed_connect_api' )
3045
				) {
3046
					/**
3047
					 * During activation, if running in AJAX mode, unless there's a sticky
3048
					 * connectivity issue notice, don't run Freemius.
3049
					 *
3050
					 * @author Vova Feldman (@svovaf)
3051
					 * @since  1.1.7.3
3052
					 */
3053
					return true;
3054
				}
3055
			}
3056
3057
			return false;
3058
		}
3059
3060
		/**
3061
		 * Triggered after code type has changed.
3062
		 *
3063
		 * @author Vova Feldman (@svovaf)
3064
		 * @since  1.1.9.1
3065
		 */
3066
		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...
3067
			$this->_logger->entrance();
3068
3069
			add_action( is_admin() ? 'admin_init' : 'init', array(
3070
				&$this,
3071
				'_plugin_code_type_changed'
3072
			) );
3073
		}
3074
3075
		/**
3076
		 * Handles plugin's code type change (free <--> premium).
3077
		 *
3078
		 * @author Vova Feldman (@svovaf)
3079
		 * @since  1.0.9
3080
		 */
3081
		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...
3082
			$this->_logger->entrance();
3083
3084
			if ( $this->is_premium() ) {
3085
				$this->reconnect_locally();
3086
3087
				// Activated premium code.
3088
				$this->do_action( 'after_premium_version_activation' );
3089
3090
				// Remove all sticky messages related to download of the premium version.
3091
				$this->_admin_notices->remove_sticky( array(
3092
					'trial_started',
3093
					'plan_upgraded',
3094
					'plan_changed',
3095
					'license_activated',
3096
				) );
3097
3098
				$this->_admin_notices->add_sticky(
3099
					$this->get_text( 'premium-activated-message' ),
3100
					'premium_activated',
3101
					$this->get_text( 'woot' ) . '!'
3102
				);
3103
			} else {
3104
				// Remove sticky message related to premium code activation.
3105
				$this->_admin_notices->remove_sticky( 'premium_activated' );
3106
3107
				// Activated free code (after had the premium before).
3108
				$this->do_action( 'after_free_version_reactivation' );
3109
3110
				if ( $this->is_paying() && ! $this->is_premium() ) {
3111
					$this->_admin_notices->add_sticky(
3112
						sprintf(
3113
							$this->get_text( 'you-have-x-license' ),
3114
							$this->_site->plan->title
3115
						) . $this->get_complete_upgrade_instructions(),
3116
						'plan_upgraded',
3117
						$this->get_text( 'yee-haw' ) . '!'
3118
					);
3119
				}
3120
			}
3121
3122
			// Schedule code type changes event.
3123
			$this->schedule_install_sync();
3124
3125
			/**
3126
			 * Unregister the uninstall hook for the other version of the plugin (with different code type) to avoid
3127
			 * triggering a fatal error when uninstalling that plugin. For example, after deactivating the "free" version
3128
			 * of a specific plugin, its uninstall hook should be unregistered after the "premium" version has been
3129
			 * activated. If we don't do that, a fatal error will occur when we try to uninstall the "free" version since
3130
			 * the main file of the "free" version will be loaded first before calling the hooked callback. Since the
3131
			 * free and premium versions are almost identical (same class or have same functions), a fatal error like
3132
			 * "Cannot redeclare class MyClass" or "Cannot redeclare my_function()" will occur.
3133
			 */
3134
			$this->unregister_uninstall_hook();
3135
3136
			$this->clear_module_main_file_cache();
3137
3138
			// Update is_premium of latest version.
3139
			$this->_storage->prev_is_premium = $this->_plugin->is_premium;
3140
		}
3141
3142
		#endregion
3143
3144
		#----------------------------------------------------------------------------------
3145
		#region Add-ons
3146
		#----------------------------------------------------------------------------------
3147
3148
		/**
3149
		 * Check if add-on installed and activated on site.
3150
		 *
3151
		 * @author Vova Feldman (@svovaf)
3152
		 * @since  1.0.6
3153
		 *
3154
		 * @param string|number $slug_or_id
3155
		 * @param bool|null     $is_premium Since 1.2.1.7 can check for specified add-on version.
3156
		 *
3157
		 * @return bool
3158
		 */
3159
		function is_addon_activated( $slug_or_id, $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...
3160
			$this->_logger->entrance();
3161
3162
			$is_activated = self::has_instance( $slug_or_id );
3163
3164
			if ( ! $is_activated ) {
3165
				return false;
3166
			}
3167
3168
			if ( is_bool( $is_premium ) ) {
3169
				// Check if the specified code version is activate.
3170
				$addon        = $this->get_addon_instance( $slug_or_id );
3171
				$is_activated = ( $is_premium === $addon->is_premium() );
3172
			}
3173
3174
			return $is_activated;
3175
		}
3176
3177
		/**
3178
		 * Check if add-on was connected to install
3179
		 *
3180
		 * @author Vova Feldman (@svovaf)
3181
		 * @since  1.1.7
3182
		 *
3183
		 * @param string $slug
3184
		 *
3185
		 * @return bool
3186
		 */
3187
		function is_addon_connected( $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...
3188
			$this->_logger->entrance();
3189
3190
			$sites = self::get_all_sites();
3191
3192
			if ( ! isset( $sites[ $slug ] ) ) {
3193
				return false;
3194
			}
3195
3196
			$site = $sites[ $slug ];
3197
3198
			$plugin = FS_Plugin_Manager::instance( $slug )->get();
3199
3200
			if ( $plugin->parent_plugin_id != $this->_plugin->id ) {
3201
				// The given slug do NOT belong to any of the plugin's add-ons.
3202
				return false;
3203
			}
3204
3205
			return ( is_object( $site ) &&
3206
			         is_numeric( $site->id ) &&
3207
			         is_numeric( $site->user_id ) &&
3208
			         is_object( $site->plan )
3209
			);
3210
		}
3211
3212
		/**
3213
		 * Determines if add-on installed.
3214
		 *
3215
		 * NOTE: This is a heuristic and only works if the folder/file named as the slug.
3216
		 *
3217
		 * @author Vova Feldman (@svovaf)
3218
		 * @since  1.0.6
3219
		 *
3220
		 * @param string $slug
3221
		 *
3222
		 * @return bool
3223
		 */
3224
		function is_addon_installed( $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...
3225
			$this->_logger->entrance();
3226
3227
			return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $slug ) ) );
3228
		}
3229
3230
		/**
3231
		 * Get add-on basename.
3232
		 *
3233
		 * @author Vova Feldman (@svovaf)
3234
		 * @since  1.0.6
3235
		 *
3236
		 * @param string $slug
3237
		 *
3238
		 * @return string
3239
		 */
3240
		function get_addon_basename( $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...
3241
			if ( $this->is_addon_activated( $slug ) ) {
3242
				self::instance( $slug )->get_plugin_basename();
3243
			}
3244
3245
			$premium_basename = "{$slug}-premium/{$slug}.php";
3246
3247
			if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_basename ) ) ) {
3248
				return $premium_basename;
3249
			}
3250
3251
			$free_basename = "{$slug}/{$slug}.php";
3252
3253
			return $free_basename;
3254
		}
3255
3256
		/**
3257
		 * Get installed add-ons instances.
3258
		 *
3259
		 * @author Vova Feldman (@svovaf)
3260
		 * @since  1.0.6
3261
		 *
3262
		 * @return Freemius[]
3263
		 */
3264
		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...
3265
			$installed_addons = array();
3266
			foreach ( self::$_instances as $slug => $instance ) {
3267
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
3268
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
3269
						$installed_addons[] = $instance;
3270
					}
3271
				}
3272
			}
3273
3274
			return $installed_addons;
3275
		}
3276
3277
		/**
3278
		 * Check if any add-ons of the plugin are installed.
3279
		 *
3280
		 * @author Leo Fajardo (@leorw)
3281
		 * @since  1.1.1
3282
		 *
3283
		 * @return bool
3284
		 */
3285
		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...
3286
			if ( ! $this->has_addons() ) {
3287
				return false;
3288
			}
3289
3290
			foreach ( self::$_instances as $slug => $instance ) {
3291
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
3292
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
3293
						return true;
3294
					}
3295
				}
3296
			}
3297
3298
			return false;
3299
		}
3300
3301
		/**
3302
		 * Tell Freemius that the current plugin is an add-on.
3303
		 *
3304
		 * @author Vova Feldman (@svovaf)
3305
		 * @since  1.0.6
3306
		 *
3307
		 * @param number $parent_plugin_id The parent plugin ID
3308
		 */
3309
		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...
3310
			$this->_plugin->parent_plugin_id = $parent_plugin_id;
3311
		}
3312
3313
		/**
3314
		 * @author Vova Feldman (@svovaf)
3315
		 * @since  1.0.6
3316
		 *
3317
		 * @return bool
3318
		 */
3319
		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...
3320
			return isset( $this->_plugin->parent_plugin_id ) && is_numeric( $this->_plugin->parent_plugin_id );
3321
		}
3322
3323
		/**
3324
		 * Deactivate add-on if it's premium only and the user does't have a valid license.
3325
		 *
3326
		 * @param bool $is_after_trial_cancel
3327
		 *
3328
		 * @return bool If add-on was deactivated.
3329
		 */
3330
		private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) {
3331
			if ( ! $this->has_free_plan() &&
3332
			     ! $this->has_features_enabled_license() &&
3333
			     ! $this->_has_premium_license()
3334
			) {
3335
				// IF wrapper is turned off because activation_timestamp is currently only stored for plugins (not addons).
3336
//                if (empty($this->_storage->activation_timestamp) ||
3337
//                    (WP_FS__SCRIPT_START_TIME - $this->_storage->activation_timestamp) > 30
3338
//                ) {
3339
				/**
3340
				 * @todo When it's first fail, there's no reason to try and re-sync because the licenses were just synced after initial activation.
3341
				 *
3342
				 * Retry syncing the user add-on licenses.
3343
				 */
3344
				// Sync licenses.
3345
				$this->_sync_licenses();
3346
//                }
3347
3348
				// Try to activate premium license.
3349
				$this->_activate_license( true );
3350
3351
				if ( ! $this->has_free_plan() &&
3352
				     ! $this->has_features_enabled_license() &&
3353
				     ! $this->_has_premium_license()
3354
				) {
3355
					// @todo Check if deactivate plugins also call the deactivation hook.
3356
3357
					deactivate_plugins( array( $this->_plugin_basename ), true );
3358
3359
					$this->_parent->_admin_notices->add_sticky(
3360
						sprintf(
3361
							$this->_parent->get_text( $is_after_trial_cancel ?
3362
								'addon-trial-cancelled-message' :
3363
								'addon-no-license-message'
3364
							),
3365
							'<b>' . $this->_plugin->title . '</b>'
3366
						) . ' ' . sprintf(
3367
							'<a href="%s" aria-label="%s" class="button button-primary" style="margin-left: 10px; vertical-align: middle;">%s &nbsp;&#10140;</a>',
3368
							$this->_parent->addon_url( $this->_slug ),
3369
							esc_attr( sprintf( $this->_parent->get_text( 'more-information-about-x' ), $this->_plugin->title ) ),
3370
							$this->_parent->get_text( 'purchase-license' )
3371
						),
3372
						'no_addon_license_' . $this->_slug,
3373
						( $is_after_trial_cancel ? '' : $this->_parent->get_text( 'oops' ) . '...' ),
3374
						( $is_after_trial_cancel ? 'success' : 'error' )
3375
					);
3376
3377
					return true;
3378
				}
3379
			}
3380
3381
			return false;
3382
		}
3383
3384
		#endregion
3385
3386
		#----------------------------------------------------------------------------------
3387
		#region Sandbox
3388
		#----------------------------------------------------------------------------------
3389
3390
		/**
3391
		 * Set Freemius into sandbox mode for debugging.
3392
		 *
3393
		 * @author Vova Feldman (@svovaf)
3394
		 * @since  1.0.4
3395
		 *
3396
		 * @param string $secret_key
3397
		 */
3398
		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...
3399
			$this->_plugin->secret_key = $secret_key;
3400
3401
			// Update plugin details.
3402
			FS_Plugin_Manager::instance( $this->_slug )->update( $this->_plugin, true );
3403
		}
3404
3405
		/**
3406
		 * Check if running payments in sandbox mode.
3407
		 *
3408
		 * @author Vova Feldman (@svovaf)
3409
		 * @since  1.0.4
3410
		 *
3411
		 * @return bool
3412
		 */
3413
		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...
3414
			return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key );
3415
		}
3416
3417
		#endregion
3418
3419
		/**
3420
		 * Check if running test vs. live plugin.
3421
		 *
3422
		 * @author Vova Feldman (@svovaf)
3423
		 * @since  1.0.5
3424
		 *
3425
		 * @return bool
3426
		 */
3427
		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...
3428
			return $this->_plugin->is_live;
3429
		}
3430
3431
		/**
3432
		 * Check if the user skipped connecting the account with Freemius.
3433
		 *
3434
		 * @author Vova Feldman (@svovaf)
3435
		 * @since  1.0.7
3436
		 *
3437
		 * @return bool
3438
		 */
3439
		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...
3440
			if ( ! isset( $this->_is_anonymous ) ) {
3441
				if ( ! isset( $this->_storage->is_anonymous ) ) {
3442
					// Not skipped.
3443
					$this->_is_anonymous = false;
3444
				} else if ( is_bool( $this->_storage->is_anonymous ) ) {
3445
					// For back compatibility, since the variable was boolean before.
3446
					$this->_is_anonymous = $this->_storage->is_anonymous;
3447
3448
					// Upgrade stored data format to 1.1.3 format.
3449
					$this->set_anonymous_mode( $this->_storage->is_anonymous );
3450
				} else {
3451
					// Version 1.1.3 and later.
3452
					$this->_is_anonymous = $this->_storage->is_anonymous['is'];
3453
				}
3454
			}
3455
3456
			return $this->_is_anonymous;
3457
		}
3458
3459
		/**
3460
		 * Check if user connected his account and install pending email activation.
3461
		 *
3462
		 * @author Vova Feldman (@svovaf)
3463
		 * @since  1.0.7
3464
		 *
3465
		 * @return bool
3466
		 */
3467
		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...
3468
			return $this->_storage->get( 'is_pending_activation', false );
3469
		}
3470
3471
		/**
3472
		 * Check if plugin must be WordPress.org compliant.
3473
		 *
3474
		 * @since 1.0.7
3475
		 *
3476
		 * @return bool
3477
		 */
3478
		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...
3479
			return $this->_is_org_compliant;
3480
		}
3481
3482
		#----------------------------------------------------------------------------------
3483
		#region Daily Sync Cron
3484
		#----------------------------------------------------------------------------------
3485
3486
		/**
3487
		 * @author Vova Feldman (@svovaf)
3488
		 * @since  1.1.7.3
3489
		 */
3490
		private function run_manual_sync() {
3491
			$this->require_pluggable_essentials();
3492
3493
			if ( ! $this->is_user_admin() ) {
3494
				return;
3495
			}
3496
3497
			// Run manual sync.
3498
			$this->_sync_cron();
3499
3500
			// Reschedule next cron to run 24 hours from now (performance optimization).
3501
			$this->clear_sync_cron();
3502
3503
			$this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false );
3504
		}
3505
3506
		/**
3507
		 * Data sync cron job. Replaces the background sync non blocking HTTP request
3508
		 * that doesn't halt page loading.
3509
		 *
3510
		 * @author Vova Feldman (@svovaf)
3511
		 * @since  1.1.7.3
3512
		 */
3513
		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...
3514
			$this->_logger->entrance();
3515
3516
			// Store the last time data sync was executed.
3517
			$this->_storage->sync_timestamp = time();
3518
3519
			// Check if API is temporary down.
3520
			if ( FS_Api::is_temporary_down() ) {
3521
				return;
3522
			}
3523
3524
			// @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours.
3525
3526
			if ( $this->is_registered() ) {
3527
				if ( $this->has_paid_plan() ) {
3528
					// Initiate background plan sync.
3529
					$this->_sync_license( true );
3530
3531
					if ( $this->is_paying() ) {
3532
						// Check for premium plugin updates.
3533
						$this->check_updates( true );
3534
					}
3535
				} else {
3536
					// Sync install (only if something changed locally).
3537
					$this->sync_install();
3538
				}
3539
			}
3540
3541
			$this->do_action( 'after_sync_cron' );
3542
		}
3543
3544
		/**
3545
		 * Check if sync was executed in the last $period of seconds.
3546
		 *
3547
		 * @author Vova Feldman (@svovaf)
3548
		 * @since  1.1.7.3
3549
		 *
3550
		 * @param int $period In seconds
3551
		 *
3552
		 * @return bool
3553
		 */
3554
		private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) {
3555
			if ( ! isset( $this->_storage->sync_timestamp ) ) {
3556
				return false;
3557
			}
3558
3559
			return ( $this->_storage->sync_timestamp > ( WP_FS__SCRIPT_START_TIME - $period ) );
3560
		}
3561
3562
		/**
3563
		 * @author Vova Feldman (@svovaf)
3564
		 * @since  1.1.7.3
3565
		 *
3566
		 * @return bool
3567
		 */
3568
		private function is_sync_cron_on() {
3569
			/**
3570
			 * @var object $sync_cron_data
3571
			 */
3572
			$sync_cron_data = $this->_storage->get( 'sync_cron', null );
3573
3574
			return ( ! is_null( $sync_cron_data ) && true === $sync_cron_data->on );
3575
		}
3576
3577
		/**
3578
		 * @author Vova Feldman (@svovaf)
3579
		 * @since  1.1.7.3
3580
		 *
3581
		 * @param int  $start_at        Defaults to now.
3582
		 * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise,
3583
		 *                              schedule job to start right away.
3584
		 */
3585
		private function schedule_sync_cron( $start_at = WP_FS__SCRIPT_START_TIME, $randomize_start = true ) {
3586
			$this->_logger->entrance();
3587
3588
			if ( $randomize_start ) {
3589
				// Schedule first sync with a random 12 hour time range from now.
3590
				$start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) );
3591
			}
3592
3593
			// Schedule daily WP cron.
3594
			wp_schedule_event(
3595
				$start_at,
3596
				'daily',
3597
				$this->get_action_tag( 'data_sync' )
3598
			);
3599
3600
			$this->_storage->store( 'sync_cron', (object) array(
3601
				'version'     => $this->get_plugin_version(),
3602
				'sdk_version' => $this->version,
3603
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
3604
				'on'          => true,
3605
			) );
3606
		}
3607
3608
		/**
3609
		 * Add the actual sync function to the cron job hook.
3610
		 *
3611
		 * @author Vova Feldman (@svovaf)
3612
		 * @since  1.1.7.3
3613
		 */
3614
		private function hook_callback_to_sync_cron() {
3615
			$this->add_action( 'data_sync', array( &$this, '_sync_cron' ) );
3616
		}
3617
3618
		/**
3619
		 * @author Vova Feldman (@svovaf)
3620
		 * @since  1.1.7.3
3621
		 */
3622
		private function clear_sync_cron() {
3623
			$this->_logger->entrance();
3624
3625
			if ( ! $this->is_sync_cron_on() ) {
3626
				return;
3627
			}
3628
3629
			$this->_storage->remove( 'sync_cron' );
3630
3631
			wp_clear_scheduled_hook( $this->get_action_tag( 'data_sync' ) );
3632
		}
3633
3634
		/**
3635
		 * Unix timestamp for next sync cron execution or false if not scheduled.
3636
		 *
3637
		 * @author Vova Feldman (@svovaf)
3638
		 * @since  1.1.7.3
3639
		 *
3640
		 * @return int|false
3641
		 */
3642
		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...
3643
			$this->_logger->entrance();
3644
3645
			if ( ! $this->is_sync_cron_on() ) {
3646
				return false;
3647
			}
3648
3649
			return wp_next_scheduled( $this->get_action_tag( 'data_sync' ) );
3650
		}
3651
3652
		/**
3653
		 * Unix timestamp for previous sync cron execution or false if never executed.
3654
		 *
3655
		 * @author Vova Feldman (@svovaf)
3656
		 * @since  1.1.7.3
3657
		 *
3658
		 * @return int|false
3659
		 */
3660
		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...
3661
			$this->_logger->entrance();
3662
3663
			return $this->_storage->get( 'sync_timestamp' );
3664
		}
3665
3666
		#endregion Daily Sync Cron ------------------------------------------------------------------
3667
3668
		#----------------------------------------------------------------------------------
3669
		#region Async Install Sync
3670
		#----------------------------------------------------------------------------------
3671
3672
		/**
3673
		 * @author Vova Feldman (@svovaf)
3674
		 * @since  1.1.7.3
3675
		 *
3676
		 * @return bool
3677
		 */
3678
		private function is_install_sync_scheduled() {
3679
			/**
3680
			 * @var object $cron_data
3681
			 */
3682
			$cron_data = $this->_storage->get( 'install_sync_cron', null );
3683
3684
			return ( ! is_null( $cron_data ) && true === $cron_data->on );
3685
		}
3686
3687
		/**
3688
		 * Instead of running blocking install sync event, execute non blocking scheduled wp-cron.
3689
		 *
3690
		 * @author Vova Feldman (@svovaf)
3691
		 * @since  1.1.7.3
3692
		 */
3693
		private function schedule_install_sync() {
3694
			$this->_logger->entrance();
3695
3696
			$this->clear_install_sync_cron();
3697
3698
			// Schedule immediate install sync.
3699
			wp_schedule_single_event(
3700
				WP_FS__SCRIPT_START_TIME,
3701
				$this->get_action_tag( 'install_sync' )
3702
			);
3703
3704
			$this->_storage->store( 'install_sync_cron', (object) array(
3705
				'version'     => $this->get_plugin_version(),
3706
				'sdk_version' => $this->version,
3707
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
3708
				'on'          => true,
3709
			) );
3710
		}
3711
3712
		/**
3713
		 * Unix timestamp for previous install sync cron execution or false if never executed.
3714
		 *
3715
		 * @todo   There's some very strange bug that $this->_storage->install_sync_timestamp value is not being
3716
		 *         updated. But for sure the sync event is working.
3717
		 *
3718
		 * @author Vova Feldman (@svovaf)
3719
		 * @since  1.1.7.3
3720
		 *
3721
		 * @return int|false
3722
		 */
3723
		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...
3724
			$this->_logger->entrance();
3725
3726
			return $this->_storage->get( 'install_sync_timestamp' );
3727
		}
3728
3729
		/**
3730
		 * Unix timestamp for next install sync cron execution or false if not scheduled.
3731
		 *
3732
		 * @author Vova Feldman (@svovaf)
3733
		 * @since  1.1.7.3
3734
		 *
3735
		 * @return int|false
3736
		 */
3737
		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...
3738
			$this->_logger->entrance();
3739
3740
			if ( ! $this->is_install_sync_scheduled() ) {
3741
				return false;
3742
			}
3743
3744
			return wp_next_scheduled( $this->get_action_tag( 'install_sync' ) );
3745
		}
3746
3747
		/**
3748
		 * Add the actual install sync function to the cron job hook.
3749
		 *
3750
		 * @author Vova Feldman (@svovaf)
3751
		 * @since  1.1.7.3
3752
		 */
3753
		private function hook_callback_to_install_sync() {
3754
			$this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) );
3755
		}
3756
3757
		/**
3758
		 * @author Vova Feldman (@svovaf)
3759
		 * @since  1.1.7.3
3760
		 */
3761
		private function clear_install_sync_cron() {
3762
			$this->_logger->entrance();
3763
3764
			if ( ! $this->is_install_sync_scheduled() ) {
3765
				return;
3766
			}
3767
3768
			$this->_storage->remove( 'install_sync_cron' );
3769
3770
			wp_clear_scheduled_hook( $this->get_action_tag( 'install_sync' ) );
3771
		}
3772
3773
		/**
3774
		 * @author Vova Feldman (@svovaf)
3775
		 * @since  1.1.7.3
3776
		 */
3777
		public function _run_sync_install() {
3778
			$this->_logger->entrance();
3779
3780
			// Update last install sync timestamp.
3781
			$this->_storage->install_sync_timestamp = time();
3782
3783
			$this->sync_install( array(), true );
3784
		}
3785
3786
		#endregion Async Install Sync ------------------------------------------------------------------
3787
3788
		/**
3789
		 * Show a notice that activation is currently pending.
3790
		 *
3791
		 * @author Vova Feldman (@svovaf)
3792
		 * @since  1.0.7
3793
		 *
3794
		 * @param bool|string $email
3795
		 * @param bool        $is_pending_trial Since 1.2.1.5
3796
		 */
3797
		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...
3798
			if ( ! is_string( $email ) ) {
3799
				$current_user = self::_get_current_wp_user();
3800
				$email        = $current_user->user_email;
3801
			}
3802
3803
			$this->_admin_notices->add_sticky(
3804
				sprintf(
3805
					$this->get_text( 'pending-activation-message' ),
3806
					'<b>' . $this->get_plugin_name() . '</b>',
3807
					'<b>' . $email . '</b>',
3808
					$this->get_text( $is_pending_trial ? 'start-the-trial' : 'complete-the-install' )
3809
				),
3810
				'activation_pending',
3811
				'Thanks!'
3812
			);
3813
		}
3814
3815
		/**
3816
		 * Check if currently in plugin activation.
3817
		 *
3818
		 * @author Vova Feldman (@svovaf)
3819
		 * @since  1.1.4
3820
		 *
3821
		 * @return bool
3822
		 */
3823
		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...
3824
			return get_option( "fs_{$this->_slug}_activated", false );
3825
		}
3826
3827
		/**
3828
		 *
3829
		 * NOTE: admin_menu action executed before admin_init.
3830
		 *
3831
		 * @author Vova Feldman (@svovaf)
3832
		 * @since  1.0.7
3833
		 */
3834
		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...
3835
			/**
3836
			 * Automatically redirect to connect/activation page after plugin activation.
3837
			 *
3838
			 * @since 1.1.7 Do NOT redirect to opt-in when running in network admin mode.
3839
			 */
3840
			if ( $this->is_plugin_activation() ) {
3841
				delete_option( "fs_{$this->_slug}_activated" );
3842
3843
				if ( ! function_exists( 'is_network_admin' ) || ! is_network_admin() ) {
3844
					$this->_redirect_on_activation_hook();
3845
3846
					return;
3847
				}
3848
			}
3849
3850
			if ( fs_request_is_action( $this->_slug . '_skip_activation' ) ) {
3851
				check_admin_referer( $this->_slug . '_skip_activation' );
3852
3853
				$this->skip_connection();
3854
3855
				fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) );
3856
			}
3857
3858
			if ( ! $this->is_addon() && ! $this->is_registered() && ! $this->is_anonymous() ) {
3859
				if ( ! $this->is_pending_activation() ) {
3860
					if ( ! $this->_menu->is_main_settings_page() ) {
3861
						if ( $this->is_plugin_new_install() || $this->is_only_premium() ) {
3862
							// Show notice for new plugin installations.
3863
							$this->_admin_notices->add(
3864
								sprintf(
3865
									$this->get_text( 'you-are-step-away' ),
3866
									sprintf( '<b><a href="%s">%s</a></b>',
3867
										$this->get_activation_url(),
3868
										sprintf( $this->get_text( 'activate-x-now' ), $this->get_plugin_name() )
3869
									)
3870
								),
3871
								'',
3872
								'update-nag'
3873
							);
3874
						} else {
3875
							if ( ! isset( $this->_storage->sticky_optin_added ) ) {
3876
								$this->_storage->sticky_optin_added = true;
3877
3878
								// Show notice for new plugin installations.
3879
								$this->_admin_notices->add_sticky(
3880
									sprintf(
3881
										$this->get_text( 'few-plugin-tweaks' ),
3882
										sprintf( '<b><a href="%s">%s</a></b>',
3883
											$this->get_activation_url(),
3884
											sprintf( $this->get_text( 'optin-x-now' ), $this->get_plugin_name() )
3885
										)
3886
									),
3887
									'connect_account',
3888
									'',
3889
									'update-nag'
3890
								);
3891
							}
3892
3893
							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...
3894
								// Don't show admin nag if plugin update.
3895
								wp_enqueue_script( 'wp-pointer' );
3896
								wp_enqueue_style( 'wp-pointer' );
3897
3898
								$this->_enqueue_connect_essentials();
3899
3900
								add_action( 'admin_print_footer_scripts', array(
3901
									$this,
3902
									'_add_connect_pointer_script'
3903
								) );
3904
							}
3905
3906
						}
3907
					}
3908
				}
3909
			}
3910
3911
			$this->_add_upgrade_action_link();
3912
		}
3913
3914
		/**
3915
		 * Enqueue connect requires scripts and styles.
3916
		 *
3917
		 * @author Vova Feldman (@svovaf)
3918
		 * @since  1.1.4
3919
		 */
3920
		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...
3921
			wp_enqueue_script( 'jquery' );
3922
			wp_enqueue_script( 'json2' );
3923
3924
			fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' );
3925
			fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' );
3926
3927
			fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' );
3928
		}
3929
3930
		/**
3931
		 * Add connect / opt-in pointer.
3932
		 *
3933
		 * @author Vova Feldman (@svovaf)
3934
		 * @since  1.1.4
3935
		 */
3936
		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...
3937
			$vars            = array( 'slug' => $this->_slug );
3938
			$pointer_content = fs_get_template( 'connect.php', $vars );
3939
			?>
3940
			<script type="text/javascript">// <![CDATA[
3941
				jQuery(document).ready(function ($) {
3942
					if ('undefined' !== typeof(jQuery().pointer)) {
3943
3944
						var element = <?php echo $this->apply_filters( 'optin_pointer_element', '$("#non_existing_element");' ) ?>;
3945
3946
						if (element.length > 0) {
3947
							var optin = $(element).pointer($.extend(true, {}, {
3948
								content     : <?php echo json_encode( $pointer_content ) ?>,
3949
								position    : {
3950
									edge : 'left',
3951
									align: 'center'
3952
								},
3953
								buttons     : function () {
3954
									// Don't show pointer buttons.
3955
									return '';
3956
								},
3957
								pointerWidth: 482
3958
							}, <?php echo $this->apply_filters( 'optin_pointer_options_json', '{}' ) ?>));
3959
3960
							<?php
3961
							echo $this->apply_filters( 'optin_pointer_execute', "
3962
3963
							optin.pointer('open');
3964
3965
							// Tag the opt-in pointer with custom class.
3966
							$('.wp-pointer #fs_connect')
3967
								.parents('.wp-pointer.wp-pointer-top')
3968
								.addClass('fs-opt-in-pointer');
3969
3970
							", 'element', 'optin' ) ?>
3971
						}
3972
					}
3973
				});
3974
				// ]]></script>
3975
			<?php
3976
		}
3977
3978
		/**
3979
		 * Return current page's URL.
3980
		 *
3981
		 * @author Vova Feldman (@svovaf)
3982
		 * @since  1.0.7
3983
		 *
3984
		 * @return string
3985
		 */
3986
		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...
3987
			$url = 'http';
3988
3989
			if ( isset( $_SERVER["HTTPS"] ) ) {
3990
				if ( $_SERVER["HTTPS"] == "on" ) {
3991
					$url .= "s";
3992
				}
3993
			}
3994
			$url .= "://";
3995
			if ( $_SERVER["SERVER_PORT"] != "80" ) {
3996
				$url .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
3997
			} else {
3998
				$url .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
3999
			}
4000
4001
			return esc_url( $url );
4002
		}
4003
4004
		/**
4005
		 * Check if the current page is the plugin's main admin settings page.
4006
		 *
4007
		 * @author Vova Feldman (@svovaf)
4008
		 * @since  1.0.7
4009
		 *
4010
		 * @return bool
4011
		 */
4012
		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...
4013
			return fs_is_plugin_page( $this->_menu->get_raw_slug() ) ||
4014
			       fs_is_plugin_page( $this->_slug );
4015
		}
4016
4017
		/* Events
4018
		------------------------------------------------------------------------------------------------------------------*/
4019
		/**
4020
		 * Delete site install from Database.
4021
		 *
4022
		 * @author Vova Feldman (@svovaf)
4023
		 * @since  1.0.1
4024
		 *
4025
		 * @param bool $store
4026
		 */
4027
		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...
4028
			$sites = self::get_all_sites();
4029
4030
			if ( isset( $sites[ $this->_slug ] ) ) {
4031
				unset( $sites[ $this->_slug ] );
4032
			}
4033
4034
			self::$_accounts->set_option( 'sites', $sites, $store );
4035
		}
4036
4037
		/**
4038
		 * Delete plugin's plans information.
4039
		 *
4040
		 * @param bool $store Flush to Database if true.
4041
		 *
4042
		 * @author Vova Feldman (@svovaf)
4043
		 * @since  1.0.9
4044
		 */
4045
		private function _delete_plans( $store = true ) {
4046
			$this->_logger->entrance();
4047
4048
			$plans = self::get_all_plans();
4049
4050
			unset( $plans[ $this->_slug ] );
4051
4052
			self::$_accounts->set_option( 'plans', $plans, $store );
4053
		}
4054
4055
		/**
4056
		 * Delete all plugin licenses.
4057
		 *
4058
		 * @author Vova Feldman (@svovaf)
4059
		 * @since  1.0.9
4060
		 *
4061
		 * @param bool        $store
4062
		 * @param string|bool $plugin_slug
4063
		 */
4064
		private function _delete_licenses( $store = true, $plugin_slug = false ) {
4065
			$this->_logger->entrance();
4066
4067
			$all_licenses = self::get_all_licenses();
4068
4069
			if ( ! is_string( $plugin_slug ) ) {
4070
				$plugin_slug = $this->_slug;
4071
			}
4072
4073
			unset( $all_licenses[ $plugin_slug ] );
4074
4075
			self::$_accounts->set_option( 'licenses', $all_licenses, $store );
4076
		}
4077
4078
		/**
4079
		 * Check if Freemius was added on new plugin installation.
4080
		 *
4081
		 * @author Vova Feldman (@svovaf)
4082
		 * @since  1.1.5
4083
		 *
4084
		 * @return bool
4085
		 */
4086
		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...
4087
			return isset( $this->_storage->is_plugin_new_install ) &&
4088
			       $this->_storage->is_plugin_new_install;
4089
		}
4090
4091
		/**
4092
		 * Check if it's the first plugin release that is running Freemius.
4093
		 *
4094
		 * @author Vova Feldman (@svovaf)
4095
		 * @since  1.2.1.5
4096
		 *
4097
		 * @return bool
4098
		 */
4099
		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...
4100
			return empty( $this->_storage->plugin_last_version );
4101
		}
4102
4103
		/**
4104
		 * Plugin activated hook.
4105
		 *
4106
		 * @author Vova Feldman (@svovaf)
4107
		 * @since  1.0.1
4108
		 *
4109
		 * @uses   FS_Api
4110
		 */
4111
		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...
4112
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4113
4114
			if ( ! $this->is_user_admin() ) {
4115
				return;
4116
			}
4117
4118
			$this->unregister_uninstall_hook();
4119
4120
			// Clear API cache on activation.
4121
			FS_Api::clear_cache();
4122
4123
			if ( $this->is_registered() ) {
4124
				$is_premium_version_activation = ( current_filter() !== ( 'activate_' . $this->_free_plugin_basename ) );
4125
4126
				if ( $is_premium_version_activation ) {
4127
					$this->reconnect_locally();
4128
				}
4129
4130
				$this->_logger->info( 'Activating ' . ( $is_premium_version_activation ? 'premium' : 'free' ) . ' plugin version.' );
4131
4132
				// 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...
4133
//				$this->sync_install( array(), true );
4134
				$this->schedule_install_sync();
4135
4136
				// 1. If running in the activation of the FREE module, get the basename of the PREMIUM.
4137
				// 2. If running in the activation of the PREMIUM module, get the basename of the FREE.
4138
				$other_version_basename = $is_premium_version_activation ?
4139
					$this->_free_plugin_basename :
4140
					$this->premium_plugin_basename();
4141
4142
				/**
4143
				 * If the other module version is activate, deactivate it.
4144
				 *
4145
				 * @author Leo Fajardo (@leorw)
4146
				 * @since  1.2.2
4147
				 */
4148
				if ( is_plugin_active( $other_version_basename ) ) {
4149
					deactivate_plugins( $other_version_basename );
4150
				}
4151
4152
				// If activating the premium module version, add an admin notice to congratulate for an upgrade completion.
4153
				if ( $is_premium_version_activation ) {
4154
					$this->_admin_notices->add(
4155
						sprintf( $this->get_text( 'successful-version-upgrade-message' ), sprintf( '<b>%s</b>', $this->_plugin->title ) ),
4156
						$this->get_text( 'woot' ) . '!'
4157
					);
4158
				}
4159
			} else if ( $this->is_anonymous() ) {
4160
				/**
4161
				 * Reset "skipped" click cache on the following:
4162
				 *  1. Development mode.
4163
				 *  2. If the user skipped the exact same version before.
4164
				 *
4165
				 * @todo 3. If explicitly asked to retry after every activation.
4166
				 */
4167
				if ( WP_FS__DEV_MODE ||
4168
				     $this->get_plugin_version() == $this->_storage->is_anonymous['version']
4169
				) {
4170
					$this->reset_anonymous_mode();
4171
				}
4172
			}
4173
4174
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
4175
				/**
4176
				 * If no previous version of plugin's version exist, it means that it's either
4177
				 * the first time that the plugin installed on the site, or the plugin was installed
4178
				 * before but didn't have Freemius integrated.
4179
				 *
4180
				 * Since register_activation_hook() do NOT fires on updates since 3.1, and only fires
4181
				 * on manual activation via the dashboard, is_plugin_activation() is TRUE
4182
				 * only after immediate activation.
4183
				 *
4184
				 * @since 1.1.4
4185
				 * @link  https://make.wordpress.org/core/2010/10/27/plugin-activation-hooks-no-longer-fire-for-updates/
4186
				 */
4187
				$this->_storage->is_plugin_new_install = empty( $this->_storage->plugin_last_version );
4188
			}
4189
4190
			if ( ! $this->_anonymous_mode &&
4191
			     $this->has_api_connectivity( WP_FS__DEV_MODE ) &&
4192
			     ! $this->_isAutoInstall
4193
			) {
4194
				// Store hint that the plugin was just activated to enable auto-redirection to settings.
4195
				add_option( "fs_{$this->_slug}_activated", true );
4196
			}
4197
4198
			/**
4199
			 * Activation hook is executed after the plugin's main file is loaded, therefore,
4200
			 * after the plugin was loaded. The logic is located at activate_plugin()
4201
			 * ./wp-admin/includes/plugin.php.
4202
			 *
4203
			 * @author Vova Feldman (@svovaf)
4204
			 * @since  1.1.9
4205
			 */
4206
			$this->_storage->was_plugin_loaded = true;
4207
		}
4208
4209
		/**
4210
		 * Delete account.
4211
		 *
4212
		 * @author Vova Feldman (@svovaf)
4213
		 * @since  1.0.3
4214
		 *
4215
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
4216
		 */
4217
		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...
4218
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4219
4220
			if ( $check_user && ! $this->is_user_admin() ) {
4221
				return;
4222
			}
4223
4224
			$this->do_action( 'before_account_delete' );
4225
4226
			// Clear all admin notices.
4227
			$this->_admin_notices->clear_all_sticky();
4228
4229
			$this->_delete_site( false );
4230
4231
			$this->_delete_plans( false );
4232
4233
			$this->_delete_licenses( false );
4234
4235
			// Delete add-ons related to plugin's account.
4236
			$this->_delete_account_addons( false );
4237
4238
			// @todo Delete plans and licenses of add-ons.
4239
4240
			self::$_accounts->store();
4241
4242
			/**
4243
			 * IMPORTANT:
4244
			 *  Clear crons must be executed before clearing all storage.
4245
			 *  Otherwise, the cron will not be cleared.
4246
			 */
4247
			$this->clear_sync_cron();
4248
			$this->clear_install_sync_cron();
4249
4250
			// Clear all storage data.
4251
			$this->_storage->clear_all( true, array(
4252
				'connectivity_test',
4253
				'is_on',
4254
			) );
4255
4256
			// Send delete event.
4257
			$this->get_api_site_scope()->call( '/', 'delete' );
4258
4259
			$this->do_action( 'after_account_delete' );
4260
		}
4261
4262
		/**
4263
		 * Plugin deactivation hook.
4264
		 *
4265
		 * @author Vova Feldman (@svovaf)
4266
		 * @since  1.0.1
4267
		 */
4268
		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...
4269
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4270
4271
			if ( ! current_user_can( 'activate_plugins' ) ) {
4272
				return;
4273
			}
4274
4275
			$this->_admin_notices->clear_all_sticky();
4276
			if ( isset( $this->_storage->sticky_optin_added ) ) {
4277
				unset( $this->_storage->sticky_optin_added );
4278
			}
4279
4280
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
4281
				// Remember that plugin was already installed.
4282
				$this->_storage->is_plugin_new_install = false;
4283
			}
4284
4285
			// Hook to plugin uninstall.
4286
			register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) );
4287
4288
			$this->clear_module_main_file_cache();
4289
			$this->clear_sync_cron();
4290
			$this->clear_install_sync_cron();
4291
4292
			if ( $this->is_registered() ) {
4293
				// Send deactivation event.
4294
				$this->sync_install( array(
4295
					'is_active' => false,
4296
				) );
4297
			} else {
4298
				if ( ! $this->has_api_connectivity() ) {
4299
					// Reset connectivity test cache.
4300
					unset( $this->_storage->connectivity_test );
4301
				}
4302
			}
4303
4304
			// Clear API cache on deactivation.
4305
			FS_Api::clear_cache();
4306
4307
			$this->remove_sdk_reference();
4308
		}
4309
4310
		/**
4311
		 * @author Vova Feldman (@svovaf)
4312
		 * @since  1.1.6
4313
		 */
4314
		private function remove_sdk_reference() {
4315
			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...
4316
4317
			foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) {
4318
				if ( $this->_plugin_basename == $data->plugin_path ) {
4319
					unset( $fs_active_plugins->plugins[ $sdk_path ] );
4320
					break;
4321
				}
4322
			}
4323
4324
			fs_fallback_to_newest_active_sdk();
4325
		}
4326
4327
		/**
4328
		 * @author Vova Feldman (@svovaf)
4329
		 * @since  1.1.3
4330
		 *
4331
		 * @param bool $is_anonymous
4332
		 */
4333
		private function set_anonymous_mode( $is_anonymous = true ) {
4334
			// Store information regarding skip to try and opt-in the user
4335
			// again in the future.
4336
			$this->_storage->is_anonymous = array(
4337
				'is'        => $is_anonymous,
4338
				'timestamp' => WP_FS__SCRIPT_START_TIME,
4339
				'version'   => $this->get_plugin_version(),
4340
			);
4341
4342
			// Update anonymous mode cache.
4343
			$this->_is_anonymous = $is_anonymous;
4344
		}
4345
4346
		/**
4347
		 * @author Vova Feldman (@svovaf)
4348
		 * @since  1.1.3
4349
		 */
4350
		private function reset_anonymous_mode() {
4351
			unset( $this->_storage->is_anonymous );
4352
		}
4353
4354
		/**
4355
		 * Clears the anonymous mode and redirects to the opt-in screen.
4356
		 *
4357
		 * @author Vova Feldman (@svovaf)
4358
		 * @since  1.1.7
4359
		 */
4360
		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...
4361
			if ( ! $this->is_anonymous() ) {
4362
				return;
4363
			}
4364
4365
			$this->reset_anonymous_mode();
4366
4367
			fs_redirect( $this->get_activation_url() );
4368
		}
4369
4370
		/**
4371
		 * Skip account connect, and set anonymous mode.
4372
		 *
4373
		 * @author Vova Feldman (@svovaf)
4374
		 * @since  1.1.1
4375
		 */
4376
		private function skip_connection() {
4377
			$this->_logger->entrance();
4378
4379
			$this->_admin_notices->remove_sticky( 'connect_account' );
4380
4381
			$this->set_anonymous_mode();
4382
4383
			// Send anonymous skip event.
4384
			// No user identified info nor any tracking will be sent after the user skips the opt-in.
4385
			$this->get_api_plugin_scope()->call( 'skip.json', 'put', array(
4386
				'uid' => $this->get_anonymous_id(),
4387
			) );
4388
		}
4389
4390
		/**
4391
		 * Plugin version update hook.
4392
		 *
4393
		 * @author Vova Feldman (@svovaf)
4394
		 * @since  1.0.4
4395
		 */
4396
		private function update_plugin_version_event() {
4397
			$this->_logger->entrance();
4398
4399
			if ( ! $this->is_registered() ) {
4400
				return;
4401
			}
4402
4403
			$this->schedule_install_sync();
4404
//			$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...
4405
		}
4406
4407
		/**
4408
		 * Return a list of modified plugins since the last sync.
4409
		 *
4410
		 * Note:
4411
		 *  There's no point to store a plugins counter since even if the number of
4412
		 *  plugins didn't change, we still need to check if the versions are all the
4413
		 *  same and the activity state is similar.
4414
		 *
4415
		 * @author Vova Feldman (@svovaf)
4416
		 * @since  1.1.8
4417
		 *
4418
		 * @return array|false
4419
		 */
4420
		private function get_plugins_data_for_api() {
4421
			// Alias.
4422
			$option_name = 'all_plugins';
4423
4424
			$all_cached_plugins = self::$_accounts->get_option( $option_name );
4425
4426
			if ( ! is_object( $all_cached_plugins ) ) {
4427
				$all_cached_plugins = (object) array(
4428
					'timestamp' => '',
4429
					'md5'       => '',
4430
					'plugins'   => array(),
4431
				);
4432
			}
4433
4434
			$time = time();
4435
4436
			if ( ! empty( $all_cached_plugins->timestamp ) &&
4437
			     ( $time - $all_cached_plugins->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC
4438
			) {
4439
				// Don't send plugin updates if last update was in the past 5 min.
4440
				return false;
4441
			}
4442
4443
			// Write timestamp to lock the logic.
4444
			$all_cached_plugins->timestamp = $time;
4445
			self::$_accounts->set_option( $option_name, $all_cached_plugins, true );
4446
4447
			// Reload options from DB.
4448
			self::$_accounts->load( true );
4449
			$all_cached_plugins = self::$_accounts->get_option( $option_name );
4450
4451
			if ( $time != $all_cached_plugins->timestamp ) {
4452
				// If timestamp is different, then another thread captured the lock.
4453
				return false;
4454
			}
4455
4456
			// Check if there's a change in plugins.
4457
			$all_plugins = self::get_all_plugins();
4458
4459
			// Check if plugins changed.
4460
			ksort( $all_plugins );
4461
4462
			$plugins_signature = '';
4463
			foreach ( $all_plugins as $basename => $data ) {
4464
				$plugins_signature .= $data['slug'] . ',' .
4465
				                      $data['Version'] . ',' .
4466
				                      ( $data['is_active'] ? '1' : '0' ) . ';';
4467
			}
4468
4469
			// Check if plugins status changed (version or active/inactive).
4470
			$plugins_changed = ( $all_cached_plugins->md5 !== md5( $plugins_signature ) );
4471
4472
			$plugins_update_data = array();
4473
4474
			if ( $plugins_changed ) {
4475
				// Change in plugins, report changes.
4476
4477
				// Update existing plugins info.
4478
				foreach ( $all_cached_plugins->plugins as $basename => $data ) {
4479
					if ( ! isset( $all_plugins[ $basename ] ) ) {
4480
						// Plugin uninstalled.
4481
						$uninstalled_plugin_data                   = $data;
4482
						$uninstalled_plugin_data['is_active']      = false;
4483
						$uninstalled_plugin_data['is_uninstalled'] = true;
4484
						$plugins_update_data[]                     = $uninstalled_plugin_data;
4485
4486
						unset( $all_plugins[ $basename ] );
4487
						unset( $all_cached_plugins->plugins[ $basename ] );
4488
					} else if ( $data['is_active'] !== $all_plugins[ $basename ]['is_active'] ||
4489
					            $data['version'] !== $all_plugins[ $basename ]['Version']
4490
					) {
4491
						// Plugin activated or deactivated, or version changed.
4492
						$all_cached_plugins->plugins[ $basename ]['is_active'] = $all_plugins[ $basename ]['is_active'];
4493
						$all_cached_plugins->plugins[ $basename ]['version']   = $all_plugins[ $basename ]['Version'];
4494
4495
						$plugins_update_data[] = $all_cached_plugins->plugins[ $basename ];
4496
					}
4497
				}
4498
4499
				// Find new plugins that weren't yet seen before.
4500
				foreach ( $all_plugins as $basename => $data ) {
4501
					if ( ! isset( $all_cached_plugins->plugins[ $basename ] ) ) {
4502
						// New plugin.
4503
						$new_plugin = array(
4504
							'slug'           => $data['slug'],
4505
							'version'        => $data['Version'],
4506
							'title'          => $data['Name'],
4507
							'is_active'      => $data['is_active'],
4508
							'is_uninstalled' => false,
4509
						);
4510
4511
						$plugins_update_data[]                    = $new_plugin;
4512
						$all_cached_plugins->plugins[ $basename ] = $new_plugin;
4513
					}
4514
				}
4515
4516
				$all_cached_plugins->md5       = md5( $plugins_signature );
4517
				$all_cached_plugins->timestamp = $time;
4518
				self::$_accounts->set_option( $option_name, $all_cached_plugins, true );
4519
			}
4520
4521
			return $plugins_update_data;
4522
		}
4523
4524
		/**
4525
		 * Return a list of modified themes since the last sync.
4526
		 *
4527
		 * Note:
4528
		 *  There's no point to store a themes counter since even if the number of
4529
		 *  themes didn't change, we still need to check if the versions are all the
4530
		 *  same and the activity state is similar.
4531
		 *
4532
		 * @author Vova Feldman (@svovaf)
4533
		 * @since  1.1.8
4534
		 *
4535
		 * @return array|false
4536
		 */
4537
		private function get_themes_data_for_api() {
4538
			// Alias.
4539
			$option_name = 'all_themes';
4540
4541
			$all_cached_themes = self::$_accounts->get_option( $option_name );
4542
4543
			if ( ! is_object( $all_cached_themes ) ) {
4544
				$all_cached_themes = (object) array(
4545
					'timestamp' => '',
4546
					'md5'       => '',
4547
					'themes'    => array(),
4548
				);
4549
			}
4550
4551
			$time = time();
4552
4553
			if ( ! empty( $all_cached_themes->timestamp ) &&
4554
			     ( $time - $all_cached_themes->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC
4555
			) {
4556
				// Don't send theme updates if last update was in the past 5 min.
4557
				return false;
4558
			}
4559
4560
			// Write timestamp to lock the logic.
4561
			$all_cached_themes->timestamp = $time;
4562
			self::$_accounts->set_option( $option_name, $all_cached_themes, true );
4563
4564
			// Reload options from DB.
4565
			self::$_accounts->load( true );
4566
			$all_cached_themes = self::$_accounts->get_option( $option_name );
4567
4568
			if ( $time != $all_cached_themes->timestamp ) {
4569
				// If timestamp is different, then another thread captured the lock.
4570
				return false;
4571
			}
4572
4573
			// Get active theme.
4574
			$active_theme            = wp_get_theme();
4575
			$active_theme_stylesheet = $active_theme->get_stylesheet();
4576
4577
			// Check if there's a change in themes.
4578
			$all_themes = wp_get_themes();
4579
4580
			// Check if themes changed.
4581
			ksort( $all_themes );
4582
4583
			$themes_signature = '';
4584
			foreach ( $all_themes as $slug => $data ) {
4585
				$is_active = ( $slug === $active_theme_stylesheet );
4586
				$themes_signature .= $slug . ',' .
4587
				                     $data->version . ',' .
4588
				                     ( $is_active ? '1' : '0' ) . ';';
4589
			}
4590
4591
			// Check if themes status changed (version or active/inactive).
4592
			$themes_changed = ( $all_cached_themes->md5 !== md5( $themes_signature ) );
4593
4594
			$themes_update_data = array();
4595
4596
			if ( $themes_changed ) {
4597
				// Change in themes, report changes.
4598
4599
				// Update existing themes info.
4600
				foreach ( $all_cached_themes->themes as $slug => $data ) {
4601
					$is_active = ( $slug === $active_theme_stylesheet );
4602
4603
					if ( ! isset( $all_themes[ $slug ] ) ) {
4604
						// Plugin uninstalled.
4605
						$uninstalled_theme_data                   = $data;
4606
						$uninstalled_theme_data['is_active']      = false;
4607
						$uninstalled_theme_data['is_uninstalled'] = true;
4608
						$themes_update_data[]                     = $uninstalled_theme_data;
4609
4610
						unset( $all_themes[ $slug ] );
4611
						unset( $all_cached_themes->themes[ $slug ] );
4612
					} else if ( $data['is_active'] !== $is_active ||
4613
					            $data['version'] !== $all_themes[ $slug ]->version
4614
					) {
4615
						// Plugin activated or deactivated, or version changed.
4616
4617
						$all_cached_themes->themes[ $slug ]['is_active'] = $is_active;
4618
						$all_cached_themes->themes[ $slug ]['version']   = $all_themes[ $slug ]->version;
4619
4620
						$themes_update_data[] = $all_cached_themes->themes[ $slug ];
4621
					}
4622
				}
4623
4624
				// Find new themes that weren't yet seen before.
4625
				foreach ( $all_themes as $slug => $data ) {
4626
					if ( ! isset( $all_cached_themes->themes[ $slug ] ) ) {
4627
						$is_active = ( $slug === $active_theme_stylesheet );
4628
4629
						// New plugin.
4630
						$new_plugin = array(
4631
							'slug'           => $slug,
4632
							'version'        => $data->version,
4633
							'title'          => $data->name,
4634
							'is_active'      => $is_active,
4635
							'is_uninstalled' => false,
4636
						);
4637
4638
						$themes_update_data[]               = $new_plugin;
4639
						$all_cached_themes->themes[ $slug ] = $new_plugin;
4640
					}
4641
				}
4642
4643
				$all_cached_themes->md5       = md5( $themes_signature );
4644
				$all_cached_themes->timestamp = time();
4645
				self::$_accounts->set_option( $option_name, $all_cached_themes, true );
4646
			}
4647
4648
			return $themes_update_data;
4649
		}
4650
4651
		/**
4652
		 * Update install details.
4653
		 *
4654
		 * @author Vova Feldman (@svovaf)
4655
		 * @since  1.1.2
4656
		 *
4657
		 * @param string[] string           $override
4658
		 * @param bool     $include_plugins Since 1.1.8 by default include plugin changes.
4659
		 * @param bool     $include_themes  Since 1.1.8 by default include plugin changes.
4660
		 *
4661
		 * @return array
4662
		 */
4663
		private function get_install_data_for_api(
4664
			array $override,
4665
			$include_plugins = true,
4666
			$include_themes = true
4667
		) {
4668
			/**
4669
			 * @since 1.1.8 Also send plugin updates.
4670
			 */
4671
			if ( $include_plugins && ! isset( $override['plugins'] ) ) {
4672
				$plugins = $this->get_plugins_data_for_api();
4673
				if ( ! empty( $plugins ) ) {
4674
					$override['plugins'] = $plugins;
4675
				}
4676
			}
4677
			/**
4678
			 * @since 1.1.8 Also send themes updates.
4679
			 */
4680
			if ( $include_themes && ! isset( $override['themes'] ) ) {
4681
				$themes = $this->get_themes_data_for_api();
4682
				if ( ! empty( $themes ) ) {
4683
					$override['themes'] = $themes;
4684
				}
4685
			}
4686
4687
			return array_merge( array(
4688
				'version'                      => $this->get_plugin_version(),
4689
				'is_premium'                   => $this->is_premium(),
4690
				'language'                     => get_bloginfo( 'language' ),
4691
				'charset'                      => get_bloginfo( 'charset' ),
4692
				'platform_version'             => get_bloginfo( 'version' ),
4693
				'sdk_version'                  => $this->version,
4694
				'programming_language_version' => phpversion(),
4695
				'title'                        => get_bloginfo( 'name' ),
4696
				'url'                          => get_site_url(),
4697
				// Special params.
4698
				'is_active'                    => true,
4699
				'is_disconnected'              => $this->is_tracking_prohibited(),
4700
				'is_uninstalled'               => false,
4701
			), $override );
4702
		}
4703
4704
		/**
4705
		 * Update install only if changed.
4706
		 *
4707
		 * @author Vova Feldman (@svovaf)
4708
		 * @since  1.0.9
4709
		 *
4710
		 * @param string[] string $override
4711
		 * @param bool     $flush
4712
		 *
4713
		 * @return false|object|string
4714
		 */
4715
		private function send_install_update( $override = array(), $flush = false ) {
4716
			$this->_logger->entrance();
4717
4718
			$check_properties = $this->get_install_data_for_api( $override );
4719
4720
			if ( $flush ) {
4721
				$params = $check_properties;
4722
			} else {
4723
				$params           = array();
4724
				$special          = array();
4725
				$special_override = false;
4726
4727
				foreach ( $check_properties as $p => $v ) {
4728
					if ( property_exists( $this->_site, $p ) ) {
4729
						if ( ( is_bool( $this->_site->{$p} ) || ! empty( $this->_site->{$p} ) ) &&
4730
						     $this->_site->{$p} != $v
4731
						) {
4732
							$this->_site->{$p} = $v;
4733
							$params[ $p ]      = $v;
4734
						}
4735
					} else {
4736
						$special[ $p ] = $v;
4737
4738
						if ( isset( $override[ $p ] ) ||
4739
						     'plugins' === $p ||
4740
						     'themes' === $p
4741
						) {
4742
							$special_override = true;
4743
						}
4744
					}
4745
				}
4746
4747
				if ( $special_override || 0 < count( $params ) ) {
4748
					// Add special params only if has at least one
4749
					// standard param, or if explicitly requested to
4750
					// override a special param or a param which is not exist
4751
					// in the install object.
4752
					$params = array_merge( $params, $special );
4753
				}
4754
			}
4755
4756
			if ( 0 < count( $params ) ) {
4757
				// Update last install sync timestamp.
4758
				$this->_storage->install_sync_timestamp = time();
4759
4760
				$params['uid'] = $this->get_anonymous_id();
4761
4762
				// Send updated values to FS.
4763
				$site = $this->get_api_site_scope()->call( '/', 'put', $params );
4764
4765
				if ( $this->is_api_result_entity( $site ) ) {
4766
					// I successfully sent install update, clear scheduled sync if exist.
4767
					$this->clear_install_sync_cron();
4768
				}
4769
4770
				return $site;
4771
			}
4772
4773
			return false;
4774
		}
4775
4776
		/**
4777
		 * Update install only if changed.
4778
		 *
4779
		 * @author Vova Feldman (@svovaf)
4780
		 * @since  1.0.9
4781
		 *
4782
		 * @param string[] string $override
4783
		 * @param bool     $flush
4784
		 */
4785
		private function sync_install( $override = array(), $flush = false ) {
4786
			$this->_logger->entrance();
4787
4788
			$site = $this->send_install_update( $override, $flush );
4789
4790
			if ( false === $site ) {
4791
				// No sync required.
4792
				return;
4793
			}
4794
4795
			if ( ! $this->is_api_result_entity( $site ) ) {
4796
				// Failed to sync, don't update locally.
4797
				return;
4798
			}
4799
4800
			$plan              = $this->get_plan();
4801
			$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...
4802
			$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...
4803
4804
			$this->_store_site( true );
4805
		}
4806
4807
		/**
4808
		 * Track install's custom event.
4809
		 *
4810
		 * IMPORTANT:
4811
		 *      Custom event tracking is currently only supported for specific clients.
4812
		 *      If you are not one of them, please don't use this method. If you will,
4813
		 *      the API will simply ignore your request based on the plugin ID.
4814
		 *
4815
		 * Need custom tracking for your plugin or theme?
4816
		 *      If you are interested in custom event tracking please contact [email protected]
4817
		 *      for further details.
4818
		 *
4819
		 * @author Vova Feldman (@svovaf)
4820
		 * @since  1.2.1
4821
		 *
4822
		 * @param string $name       Event name.
4823
		 * @param array  $properties Associative key/value array with primitive values only
4824
		 * @param bool   $process_at A valid future date-time in the following format Y-m-d H:i:s.
4825
		 * @param bool   $once       If true, event will be tracked only once. IMPORTANT: Still trigger the API call.
4826
		 *
4827
		 * @return object|false Event data or FALSE on failure.
4828
		 *
4829
		 * @throws \Freemius_InvalidArgumentException
4830
		 */
4831
		public function track_event( $name, $properties = array(), $process_at = false, $once = false ) {
4832
			$this->_logger->entrance( http_build_query( array( 'name' => $name, 'once' => $once ) ) );
4833
4834
			if ( ! $this->is_registered() ) {
4835
				return false;
4836
			}
4837
4838
			$event = array( 'type' => $name );
4839
4840
			if ( is_numeric( $process_at ) && $process_at > time() ) {
4841
				$event['process_at'] = $process_at;
4842
			}
4843
4844
			if ( $once ) {
4845
				$event['once'] = true;
4846
			}
4847
4848
			if ( ! empty( $properties ) ) {
4849
				// Verify associative array values are primitive.
4850
				foreach ( $properties as $k => $v ) {
4851
					if ( ! is_scalar( $v ) ) {
4852
						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...
4853
					}
4854
				}
4855
4856
				$event['properties'] = $properties;
4857
			}
4858
4859
			$result = $this->get_api_site_scope()->call( 'events.json', 'post', $event );
4860
4861
			return $this->is_api_error( $result ) ?
4862
				false :
4863
				$result;
4864
		}
4865
4866
		/**
4867
		 * Track install's custom event only once, but it still triggers the API call.
4868
		 *
4869
		 * IMPORTANT:
4870
		 *      Custom event tracking is currently only supported for specific clients.
4871
		 *      If you are not one of them, please don't use this method. If you will,
4872
		 *      the API will simply ignore your request based on the plugin ID.
4873
		 *
4874
		 * Need custom tracking for your plugin or theme?
4875
		 *      If you are interested in custom event tracking please contact [email protected]
4876
		 *      for further details.
4877
		 *
4878
		 * @author Vova Feldman (@svovaf)
4879
		 * @since  1.2.1
4880
		 *
4881
		 * @param string $name       Event name.
4882
		 * @param array  $properties Associative key/value array with primitive values only
4883
		 * @param bool   $process_at A valid future date-time in the following format Y-m-d H:i:s.
4884
		 *
4885
		 * @return object|false Event data or FALSE on failure.
4886
		 *
4887
		 * @throws \Freemius_InvalidArgumentException
4888
		 *
4889
		 * @user   Freemius::track_event()
4890
		 */
4891
		public function track_event_once( $name, $properties = array(), $process_at = false ) {
4892
			return $this->track_event( $name, $properties, $process_at, true );
4893
		}
4894
4895
		/**
4896
		 * Plugin uninstall hook.
4897
		 *
4898
		 * @author Vova Feldman (@svovaf)
4899
		 * @since  1.0.1
4900
		 *
4901
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
4902
		 */
4903
		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...
4904
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4905
4906
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
4907
				return;
4908
			}
4909
4910
			$params           = array();
4911
			$uninstall_reason = null;
4912
			if ( isset( $this->_storage->uninstall_reason ) ) {
4913
				$uninstall_reason      = $this->_storage->uninstall_reason;
4914
				$params['reason_id']   = $uninstall_reason->id;
4915
				$params['reason_info'] = $uninstall_reason->info;
4916
			}
4917
4918
			if ( ! $this->is_registered() ) {
4919
				// Send anonymous uninstall event only if user submitted a feedback.
4920
				if ( isset( $uninstall_reason ) ) {
4921
					if ( isset( $uninstall_reason->is_anonymous ) && ! $uninstall_reason->is_anonymous ) {
4922
						$this->opt_in( false, false, false, false, true );
4923
					} else {
4924
						$params['uid'] = $this->get_anonymous_id();
4925
						$this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params );
4926
					}
4927
				}
4928
			} else {
4929
				// Send uninstall event.
4930
				$this->send_install_update( array_merge( $params, array(
4931
					'is_active'      => false,
4932
					'is_uninstalled' => true,
4933
				) ) );
4934
			}
4935
4936
			// @todo Decide if we want to delete plugin information from db.
4937
		}
4938
4939
		/**
4940
		 * @author Vova Feldman (@svovaf)
4941
		 * @since  1.1.1
4942
		 *
4943
		 * @return string
4944
		 */
4945
		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...
4946
			return "{$this->_slug}-premium/" . basename( $this->_free_plugin_basename );
4947
		}
4948
4949
		/**
4950
		 * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking.
4951
		 *
4952
		 * @author Vova Feldman (@svovaf)
4953
		 * @since  1.0.2
4954
		 */
4955
		public static function _uninstall_plugin_hook() {
4956
			self::_load_required_static();
4957
4958
			self::$_static_logger->entrance();
4959
4960
			if ( ! current_user_can( 'activate_plugins' ) ) {
4961
				return;
4962
			}
4963
4964
			$plugin_file = substr( current_filter(), strlen( 'uninstall_' ) );
4965
4966
			self::$_static_logger->info( 'plugin = ' . $plugin_file );
4967
4968
			define( 'WP_FS__UNINSTALL_MODE', true );
4969
4970
			$fs = self::get_instance_by_file( $plugin_file );
4971
4972
			if ( is_object( $fs ) ) {
4973
				self::require_plugin_essentials();
4974
4975
				if ( is_plugin_active( $fs->_free_plugin_basename ) ||
4976
				     is_plugin_active( $fs->premium_plugin_basename() )
4977
				) {
4978
					// Deleting Free or Premium plugin version while the other version still installed.
4979
					return;
4980
				}
4981
4982
				$fs->_uninstall_plugin_event();
4983
4984
				$fs->do_action( 'after_uninstall' );
4985
			}
4986
		}
4987
4988
		#----------------------------------------------------------------------------------
4989
		#region Plugin Information
4990
		#----------------------------------------------------------------------------------
4991
4992
		/**
4993
		 * Load WordPress core plugin.php essential module.
4994
		 *
4995
		 * @author Vova Feldman (@svovaf)
4996
		 * @since  1.1.1
4997
		 */
4998
		private static function require_plugin_essentials() {
4999
			if ( ! function_exists( 'get_plugins' ) ) {
5000
				self::$_static_logger->log( 'Including wp-admin/includes/plugin.php...' );
5001
5002
				require_once ABSPATH . 'wp-admin/includes/plugin.php';
5003
			}
5004
		}
5005
5006
		/**
5007
		 * Load WordPress core pluggable.php module.
5008
		 *
5009
		 * @author Vova Feldman (@svovaf)
5010
		 * @since  1.1.2
5011
		 */
5012
		private static function require_pluggable_essentials() {
5013
			if ( ! function_exists( 'wp_get_current_user' ) ) {
5014
				require_once ABSPATH . 'wp-includes/pluggable.php';
5015
			}
5016
		}
5017
5018
		/**
5019
		 * Return plugin data.
5020
		 *
5021
		 * @author Vova Feldman (@svovaf)
5022
		 * @since  1.0.1
5023
		 *
5024
		 * @return array
5025
		 */
5026
		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...
5027
			if ( ! isset( $this->_plugin_data ) ) {
5028
				self::require_plugin_essentials();
5029
5030
				/**
5031
				 * @author Vova Feldman (@svovaf)
5032
				 * @since  1.2.0 When using get_plugin_data() do NOT translate plugin data.
5033
				 *
5034
				 * @link   https://github.com/Freemius/wordpress-sdk/issues/77
5035
				 */
5036
				$this->_plugin_data = get_plugin_data(
5037
					$this->_plugin_main_file_path,
5038
					false,
5039
					false
5040
				);
5041
			}
5042
5043
			return $this->_plugin_data;
5044
		}
5045
5046
		/**
5047
		 * @author Vova Feldman (@svovaf)
5048
		 * @since  1.0.1
5049
		 *
5050
		 * @return string Plugin slug.
5051
		 */
5052
		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...
5053
			return $this->_slug;
5054
		}
5055
5056
		/**
5057
		 * @author Vova Feldman (@svovaf)
5058
		 * @since  1.2.1.7
5059
		 *
5060
		 * @return string Plugin slug.
5061
		 */
5062
		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...
5063
			return $this->_slug . ( $this->can_use_premium_code() ? '-premium' : '' );
5064
		}
5065
5066
		/**
5067
		 * @author Vova Feldman (@svovaf)
5068
		 * @since  1.0.1
5069
		 *
5070
		 * @return number Plugin ID.
5071
		 */
5072
		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...
5073
			return $this->_plugin->id;
5074
		}
5075
5076
		/**
5077
		 * @author Vova Feldman (@svovaf)
5078
		 * @since  1.2.1.5
5079
		 *
5080
		 * @return string Freemius SDK version
5081
		 */
5082
		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...
5083
			return $this->version;
5084
		}
5085
5086
		/**
5087
		 * @author Vova Feldman (@svovaf)
5088
		 * @since  1.2.1.5
5089
		 *
5090
		 * @return number Parent plugin ID (if parent exist).
5091
		 */
5092
		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...
5093
			return $this->is_addon() ?
5094
				$this->get_parent_instance()->get_id() :
5095
				$this->_plugin->id;
5096
		}
5097
5098
		/**
5099
		 * @author Vova Feldman (@svovaf)
5100
		 * @since  1.0.1
5101
		 *
5102
		 * @return string Plugin public key.
5103
		 */
5104
		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...
5105
			return $this->_plugin->public_key;
5106
		}
5107
5108
		/**
5109
		 * Will be available only on sandbox mode.
5110
		 *
5111
		 * @author Vova Feldman (@svovaf)
5112
		 * @since  1.0.4
5113
		 *
5114
		 * @return mixed Plugin secret key.
5115
		 */
5116
		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...
5117
			return $this->_plugin->secret_key;
5118
		}
5119
5120
		/**
5121
		 * @author Vova Feldman (@svovaf)
5122
		 * @since  1.1.1
5123
		 *
5124
		 * @return bool
5125
		 */
5126
		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...
5127
			return ! empty( $this->_plugin->secret_key );
5128
		}
5129
5130
		/**
5131
		 * @author Vova Feldman (@svovaf)
5132
		 * @since  1.0.9
5133
		 *
5134
		 * @return string
5135
		 */
5136
		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...
5137
			$this->_logger->entrance();
5138
5139
			if ( ! isset( $this->_plugin_name ) ) {
5140
				$plugin_data = $this->get_plugin_data();
5141
5142
				// Get name.
5143
				$this->_plugin_name = $plugin_data['Name'];
5144
5145
				// Check if plugin name contains [Premium] suffix and remove it.
5146
				$suffix     = '[premium]';
5147
				$suffix_len = strlen( $suffix );
5148
5149
				if ( strlen( $plugin_data['Name'] ) > $suffix_len &&
5150
				     $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len )
5151
				) {
5152
					$this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len );
5153
				}
5154
5155
				$this->_logger->departure( 'Name = ' . $this->_plugin_name );
5156
			}
5157
5158
			return $this->_plugin_name;
5159
		}
5160
5161
		/**
5162
		 * @author Vova Feldman (@svovaf)
5163
		 * @since  1.0.0
5164
		 *
5165
		 * @return string
5166
		 */
5167
		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...
5168
			$this->_logger->entrance();
5169
5170
			$plugin_data = $this->get_plugin_data();
5171
5172
			$this->_logger->departure( 'Version = ' . $plugin_data['Version'] );
5173
5174
			return $this->apply_filters( 'plugin_version', $plugin_data['Version'] );
5175
		}
5176
5177
		/**
5178
		 * @author Vova Feldman (@svovaf)
5179
		 * @since  1.2.1.7
5180
		 *
5181
		 * @return string
5182
		 */
5183
		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...
5184
			$this->_logger->entrance();
5185
5186
			$title = $this->_plugin->title;
5187
5188
			return $this->apply_filters( 'plugin_title', $title );
5189
		}
5190
5191
		/**
5192
		 * @author Vova Feldman (@svovaf)
5193
		 * @since  1.0.4
5194
		 *
5195
		 * @return string
5196
		 */
5197
		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...
5198
			return $this->_plugin_basename;
5199
		}
5200
5201
		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...
5202
			$this->_logger->entrance();
5203
5204
			$plugin_folder = $this->_plugin_basename;
5205
5206
			while ( '.' !== dirname( $plugin_folder ) ) {
5207
				$plugin_folder = dirname( $plugin_folder );
5208
			}
5209
5210
			$this->_logger->departure( 'Folder Name = ' . $plugin_folder );
5211
5212
			return $plugin_folder;
5213
		}
5214
5215
		#endregion ------------------------------------------------------------------
5216
5217
		/* Account
5218
		------------------------------------------------------------------------------------------------------------------*/
5219
5220
		/**
5221
		 * Find plugin's slug by plugin's basename.
5222
		 *
5223
		 * @author Vova Feldman (@svovaf)
5224
		 * @since  1.0.9
5225
		 *
5226
		 * @param string $plugin_base_name
5227
		 *
5228
		 * @return false|string
5229
		 */
5230
		private static function find_slug_by_basename( $plugin_base_name ) {
5231
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
5232
5233
			if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) {
5234
				return false;
5235
			}
5236
5237
			return $file_slug_map[ $plugin_base_name ];
5238
		}
5239
5240
		/**
5241
		 * Store the map between the plugin's basename to the slug.
5242
		 *
5243
		 * @author Vova Feldman (@svovaf)
5244
		 * @since  1.0.9
5245
		 */
5246
		private function store_file_slug_map() {
5247
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
5248
5249
			if ( ! array( $file_slug_map ) ) {
5250
				$file_slug_map = array();
5251
			}
5252
5253
			if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) ||
5254
			     $file_slug_map[ $this->_plugin_basename ] !== $this->_slug
5255
			) {
5256
				$file_slug_map[ $this->_plugin_basename ] = $this->_slug;
5257
				self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true );
5258
			}
5259
		}
5260
5261
		/**
5262
		 * @return FS_User[]
5263
		 */
5264
		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...
5265
			$users = self::$_accounts->get_option( 'users', array() );
5266
5267
			if ( ! is_array( $users ) ) {
5268
				$users = array();
5269
			}
5270
5271
			return $users;
5272
		}
5273
5274
		/**
5275
		 * @return FS_Site[]
5276
		 */
5277
		private static function get_all_sites() {
5278
			$sites = self::$_accounts->get_option( 'sites', array() );
5279
5280
			if ( ! is_array( $sites ) ) {
5281
				$sites = array();
5282
			}
5283
5284
			return $sites;
5285
		}
5286
5287
		/**
5288
		 * @author Vova Feldman (@svovaf)
5289
		 * @since  1.0.6
5290
		 *
5291
		 * @return FS_Plugin_License[]
5292
		 */
5293
		private static function get_all_licenses() {
5294
			$licenses = self::$_accounts->get_option( 'licenses', array() );
5295
5296
			if ( ! is_array( $licenses ) ) {
5297
				$licenses = array();
5298
			}
5299
5300
			return $licenses;
5301
		}
5302
5303
		/**
5304
		 * @return FS_Plugin_Plan[]
5305
		 */
5306
		private static function get_all_plans() {
5307
			$plans = self::$_accounts->get_option( 'plans', array() );
5308
5309
			if ( ! is_array( $plans ) ) {
5310
				$plans = array();
5311
			}
5312
5313
			return $plans;
5314
		}
5315
5316
		/**
5317
		 * @author Vova Feldman (@svovaf)
5318
		 * @since  1.0.4
5319
		 *
5320
		 * @return FS_Plugin_Tag[]
5321
		 */
5322
		private static function get_all_updates() {
5323
			$updates = self::$_accounts->get_option( 'updates', array() );
5324
5325
			if ( ! is_array( $updates ) ) {
5326
				$updates = array();
5327
			}
5328
5329
			return $updates;
5330
		}
5331
5332
		/**
5333
		 * @author Vova Feldman (@svovaf)
5334
		 * @since  1.0.6
5335
		 *
5336
		 * @return array<number,FS_Plugin[]>|false
5337
		 */
5338
		private static function get_all_addons() {
5339
			$addons = self::$_accounts->get_option( 'addons', array() );
5340
5341
			if ( ! is_array( $addons ) ) {
5342
				$addons = array();
5343
			}
5344
5345
			return $addons;
5346
		}
5347
5348
		/**
5349
		 * @author Vova Feldman (@svovaf)
5350
		 * @since  1.0.6
5351
		 *
5352
		 * @return FS_Plugin[]|false
5353
		 */
5354
		private static function get_all_account_addons() {
5355
			$addons = self::$_accounts->get_option( 'account_addons', array() );
5356
5357
			if ( ! is_array( $addons ) ) {
5358
				$addons = array();
5359
			}
5360
5361
			return $addons;
5362
		}
5363
5364
		/**
5365
		 * Check if user has connected his account (opted-in).
5366
		 *
5367
		 * Note:
5368
		 *      If the user opted-in and opted-out on a later stage,
5369
		 *      this will still return true. If you want to check if the
5370
		 *      user is currently opted-in, use:
5371
		 *          `$fs->is_registered() && $fs->is_tracking_allowed()`
5372
		 *
5373
		 * @author Vova Feldman (@svovaf)
5374
		 * @since  1.0.1
5375
		 * @return bool
5376
		 */
5377
		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...
5378
			return is_object( $this->_user );
5379
		}
5380
5381
		/**
5382
		 * Returns TRUE if the user opted-in and didn't disconnect (opt-out).
5383
		 *
5384
		 * @author Leo Fajardo (@leorw)
5385
		 * @since  1.2.1.5
5386
		 *
5387
		 * @return bool
5388
		 */
5389
		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...
5390
			return ( is_object( $this->_site ) && true !== $this->_site->is_disconnected );
5391
		}
5392
5393
		/**
5394
		 * @author Vova Feldman (@svovaf)
5395
		 * @since  1.0.4
5396
		 *
5397
		 * @return FS_Plugin
5398
		 */
5399
		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...
5400
			return $this->_plugin;
5401
		}
5402
5403
		/**
5404
		 * @author Vova Feldman (@svovaf)
5405
		 * @since  1.0.3
5406
		 *
5407
		 * @return FS_User
5408
		 */
5409
		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...
5410
			return $this->_user;
5411
		}
5412
5413
		/**
5414
		 * @author Vova Feldman (@svovaf)
5415
		 * @since  1.0.3
5416
		 *
5417
		 * @return FS_Site
5418
		 */
5419
		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...
5420
			return $this->_site;
5421
		}
5422
5423
		/**
5424
		 * Get plugin add-ons.
5425
		 *
5426
		 * @author Vova Feldman (@svovaf)
5427
		 * @since  1.0.6
5428
		 *
5429
		 * @since  1.1.7.3 If not yet loaded, fetch data from the API.
5430
		 *
5431
		 * @param bool $flush
5432
		 *
5433
		 * @return FS_Plugin[]|false
5434
		 */
5435
		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...
5436
			$this->_logger->entrance();
5437
5438
			if ( ! $this->_has_addons ) {
5439
				return false;
5440
			}
5441
5442
			$addons = $this->sync_addons( $flush );
5443
5444
			return ( ! is_array( $addons ) || empty( $addons ) ) ?
5445
				false :
5446
				$addons;
5447
		}
5448
5449
		/**
5450
		 * @author Vova Feldman (@svovaf)
5451
		 * @since  1.0.6
5452
		 *
5453
		 * @return FS_Plugin[]|false
5454
		 */
5455
		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...
5456
			$this->_logger->entrance();
5457
5458
			$addons = self::get_all_account_addons();
5459
5460
			if ( ! is_array( $addons ) ||
5461
			     ! isset( $addons[ $this->_plugin->id ] ) ||
5462
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
5463
			     0 === count( $addons[ $this->_plugin->id ] )
5464
			) {
5465
				return false;
5466
			}
5467
5468
			return $addons[ $this->_plugin->id ];
5469
		}
5470
5471
		/**
5472
		 * Check if user has any
5473
		 *
5474
		 * @author Vova Feldman (@svovaf)
5475
		 * @since  1.1.6
5476
		 *
5477
		 * @return bool
5478
		 */
5479
		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...
5480
			$addons = $this->get_account_addons();
5481
5482
			return is_array( $addons ) && ( 0 < count( $addons ) );
5483
		}
5484
5485
5486
		/**
5487
		 * Get add-on by ID (from local data).
5488
		 *
5489
		 * @author Vova Feldman (@svovaf)
5490
		 * @since  1.0.6
5491
		 *
5492
		 * @param number $id
5493
		 *
5494
		 * @return FS_Plugin|false
5495
		 */
5496
		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...
5497
			$this->_logger->entrance();
5498
5499
			$addons = $this->get_addons();
5500
5501
			if ( is_array( $addons ) ) {
5502
				foreach ( $addons as $addon ) {
5503
					if ( $id == $addon->id ) {
5504
						return $addon;
5505
					}
5506
				}
5507
			}
5508
5509
			return false;
5510
		}
5511
5512
		/**
5513
		 * Get add-on by slug (from local data).
5514
		 *
5515
		 * @author Vova Feldman (@svovaf)
5516
		 * @since  1.0.6
5517
		 *
5518
		 * @param string $slug
5519
		 *
5520
		 * @param bool   $flush
5521
		 *
5522
		 * @return FS_Plugin|false
5523
		 */
5524
		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...
5525
			$this->_logger->entrance();
5526
5527
			$addons = $this->get_addons( $flush );
5528
5529
			if ( is_array( $addons ) ) {
5530
				foreach ( $addons as $addon ) {
5531
					if ( $slug === $addon->slug ) {
5532
						return $addon;
5533
					}
5534
				}
5535
			}
5536
5537
			return false;
5538
		}
5539
5540
		#----------------------------------------------------------------------------------
5541
		#region Plans & Licensing
5542
		#----------------------------------------------------------------------------------
5543
5544
		/**
5545
		 * Check if running premium plugin code.
5546
		 *
5547
		 * @author Vova Feldman (@svovaf)
5548
		 * @since  1.0.5
5549
		 *
5550
		 * @return bool
5551
		 */
5552
		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...
5553
			return $this->_plugin->is_premium;
5554
		}
5555
5556
		/**
5557
		 * Get site's plan ID.
5558
		 *
5559
		 * @author Vova Feldman (@svovaf)
5560
		 * @since  1.0.2
5561
		 *
5562
		 * @return number
5563
		 */
5564
		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...
5565
			return $this->_site->plan->id;
5566
		}
5567
5568
		/**
5569
		 * Get site's plan title.
5570
		 *
5571
		 * @author Vova Feldman (@svovaf)
5572
		 * @since  1.0.2
5573
		 *
5574
		 * @return string
5575
		 */
5576
		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...
5577
			return $this->_site->plan->title;
5578
		}
5579
5580
		/**
5581
		 * @author Vova Feldman (@svovaf)
5582
		 * @since  1.0.9
5583
		 *
5584
		 * @return FS_Plugin_Plan|false
5585
		 */
5586
		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...
5587
			return is_object( $this->_site->plan ) ?
5588
				$this->_site->plan :
5589
				false;
5590
		}
5591
5592
		/**
5593
		 * @author Vova Feldman (@svovaf)
5594
		 * @since  1.0.3
5595
		 *
5596
		 * @return bool
5597
		 */
5598
		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...
5599
			$this->_logger->entrance();
5600
5601
			if ( ! $this->is_registered() ) {
5602
				return false;
5603
			}
5604
5605
			return $this->_site->is_trial();
5606
		}
5607
5608
		/**
5609
		 * Check if currently in a trial with payment method (credit card or paypal).
5610
		 *
5611
		 * @author Vova Feldman (@svovaf)
5612
		 * @since  1.1.7
5613
		 *
5614
		 * @return bool
5615
		 */
5616
		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...
5617
			$this->_logger->entrance();
5618
5619
			if ( ! $this->is_trial() ) {
5620
				return false;
5621
			}
5622
5623
			return $this->has_active_valid_license() && ( $this->_site->trial_plan_id == $this->_license->plan_id );
5624
		}
5625
5626
		/**
5627
		 * Check if trial already utilized.
5628
		 *
5629
		 * @since 1.0.9
5630
		 *
5631
		 * @return bool
5632
		 */
5633
		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...
5634
			$this->_logger->entrance();
5635
5636
			if ( ! $this->is_registered() ) {
5637
				return false;
5638
			}
5639
5640
			return $this->_site->is_trial_utilized();
5641
		}
5642
5643
		/**
5644
		 * Get trial plan information (if in trial).
5645
		 *
5646
		 * @author Vova Feldman (@svovaf)
5647
		 * @since  1.0.9
5648
		 *
5649
		 * @return bool|FS_Plugin_Plan
5650
		 */
5651
		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...
5652
			$this->_logger->entrance();
5653
5654
			if ( ! $this->is_trial() ) {
5655
				return false;
5656
			}
5657
5658
			return $this->_storage->trial_plan;
5659
		}
5660
5661
		/**
5662
		 * Check if the user has an activate, non-expired license on current plugin's install.
5663
		 *
5664
		 * @since 1.0.9
5665
		 *
5666
		 * @return bool
5667
		 */
5668
		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...
5669
			$this->_logger->entrance();
5670
5671
			if ( ! $this->is_registered() ) {
5672
				return false;
5673
			}
5674
5675
			if ( ! $this->has_paid_plan() ) {
5676
				return false;
5677
			}
5678
5679
			return (
5680
				! $this->is_trial() &&
5681
				'free' !== $this->_site->plan->name &&
5682
				$this->has_active_valid_license()
5683
			);
5684
		}
5685
5686
		/**
5687
		 * @author Vova Feldman (@svovaf)
5688
		 * @since  1.0.4
5689
		 *
5690
		 * @return bool
5691
		 */
5692
		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...
5693
			if ( ! $this->is_registered() ) {
5694
				return true;
5695
			}
5696
5697
			if ( ! $this->has_paid_plan() ) {
5698
				return true;
5699
			}
5700
5701
			return (
5702
				'free' === $this->_site->plan->name ||
5703
				! $this->has_features_enabled_license()
5704
			);
5705
		}
5706
5707
		/**
5708
		 * @author Vova Feldman (@svovaf)
5709
		 * @since  1.0.5
5710
		 *
5711
		 * @return bool
5712
		 */
5713
		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...
5714
			$this->_logger->entrance();
5715
5716
			$premium_license = $this->_get_available_premium_license();
5717
5718
			return ( false !== $premium_license );
5719
		}
5720
5721
		/**
5722
		 * Check if user has any licenses associated with the plugin (including expired or blocking).
5723
		 *
5724
		 * @author Vova Feldman (@svovaf)
5725
		 * @since  1.1.7.3
5726
		 *
5727
		 * @return bool
5728
		 */
5729
		private function has_any_license() {
5730
			return is_array( $this->_licenses ) && ( 0 < count( $this->_licenses ) );
5731
		}
5732
5733
		/**
5734
		 * @author Vova Feldman (@svovaf)
5735
		 * @since  1.0.5
5736
		 *
5737
		 * @return FS_Plugin_License|false
5738
		 */
5739
		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...
5740
			$this->_logger->entrance();
5741
5742
			if ( ! $this->has_paid_plan() ) {
5743
				return false;
5744
			}
5745
5746
			if ( is_array( $this->_licenses ) ) {
5747
				foreach ( $this->_licenses as $license ) {
5748
					if ( ! $license->is_utilized() && $license->is_features_enabled() ) {
5749
						return $license;
5750
					}
5751
				}
5752
			}
5753
5754
			return false;
5755
		}
5756
5757
		/**
5758
		 * Sync local plugin plans with remote server.
5759
		 *
5760
		 * @author Vova Feldman (@svovaf)
5761
		 * @since  1.0.5
5762
		 *
5763
		 * @return FS_Plugin_Plan[]|object
5764
		 */
5765
		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...
5766
			$plans = $this->_fetch_plugin_plans();
5767
5768
			if ( $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) {
5769
				$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...
5770
				$this->_store_plans();
5771
			}
5772
5773
			$this->do_action( 'after_plans_sync', $plans );
5774
5775
			return $this->_plans;
5776
		}
5777
5778
		/**
5779
		 * @author Vova Feldman (@svovaf)
5780
		 * @since  1.0.5
5781
		 *
5782
		 * @param number $id
5783
		 *
5784
		 * @return FS_Plugin_Plan|false
5785
		 */
5786
		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...
5787
			$this->_logger->entrance();
5788
5789
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
5790
				$this->_sync_plans();
5791
			}
5792
5793
			foreach ( $this->_plans as $plan ) {
5794
				if ( $id == $plan->id ) {
5795
					return $plan;
5796
				}
5797
			}
5798
5799
			return false;
5800
		}
5801
5802
		/**
5803
		 * @author Vova Feldman (@svovaf)
5804
		 * @since  1.1.8.1
5805
		 *
5806
		 * @param string $name
5807
		 *
5808
		 * @return FS_Plugin_Plan|false
5809
		 */
5810
		private function get_plan_by_name( $name ) {
5811
			$this->_logger->entrance();
5812
5813
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
5814
				$this->_sync_plans();
5815
			}
5816
5817
			foreach ( $this->_plans as $plan ) {
5818
				if ( $name == $plan->name ) {
5819
					return $plan;
5820
				}
5821
			}
5822
5823
			return false;
5824
		}
5825
5826
		/**
5827
		 * Sync local plugin plans with remote server.
5828
		 *
5829
		 * @author Vova Feldman (@svovaf)
5830
		 * @since  1.0.6
5831
		 *
5832
		 * @param number|bool $site_license_id
5833
		 *
5834
		 * @return FS_Plugin_License[]|object
5835
		 */
5836
		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...
5837
			$licenses = $this->_fetch_licenses( false, $site_license_id );
5838
5839
			if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) {
5840
				$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...
5841
				$this->_store_licenses();
5842
			}
5843
5844
			// Update current license.
5845
			if ( is_object( $this->_license ) ) {
5846
				$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...
5847
			}
5848
5849
			return $this->_licenses;
5850
		}
5851
5852
		/**
5853
		 * @author Vova Feldman (@svovaf)
5854
		 * @since  1.0.5
5855
		 *
5856
		 * @param number $id
5857
		 *
5858
		 * @return FS_Plugin_License|false
5859
		 */
5860
		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...
5861
			$this->_logger->entrance();
5862
5863
			if ( ! is_numeric( $id ) ) {
5864
				return false;
5865
			}
5866
5867
			if ( ! $this->has_any_license() ) {
5868
				$this->_sync_licenses();
5869
			}
5870
5871
			foreach ( $this->_licenses as $license ) {
5872
				if ( $id == $license->id ) {
5873
					return $license;
5874
				}
5875
			}
5876
5877
			return false;
5878
		}
5879
5880
		/**
5881
		 * Sync site's license with user licenses.
5882
		 *
5883
		 * @author Vova Feldman (@svovaf)
5884
		 * @since  1.0.6
5885
		 *
5886
		 * @param FS_Plugin_License|null $new_license
5887
		 */
5888
		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...
5889
			$this->_logger->entrance();
5890
5891
			$this->_license = $new_license;
5892
5893
			if ( ! is_object( $new_license ) ) {
5894
				$this->_site->license_id = null;
5895
				$this->_sync_site_subscription( null );
5896
5897
				return;
5898
			}
5899
5900
			$this->_site->license_id = $this->_license->id;
5901
5902
			if ( ! is_array( $this->_licenses ) ) {
5903
				$this->_licenses = array();
5904
			}
5905
5906
			$is_license_found = false;
5907
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
5908
				if ( $new_license->id == $this->_licenses[ $i ]->id ) {
5909
					$this->_licenses[ $i ] = $new_license;
5910
5911
					$is_license_found = true;
5912
					break;
5913
				}
5914
			}
5915
5916
			// If new license just append.
5917
			if ( ! $is_license_found ) {
5918
				$this->_licenses[] = $new_license;
5919
			}
5920
5921
			$this->_sync_site_subscription( $new_license );
5922
		}
5923
5924
		/**
5925
		 * Sync site's subscription.
5926
		 *
5927
		 * @author Vova Feldman (@svovaf)
5928
		 * @since  1.0.9
5929
		 *
5930
		 * @param FS_Plugin_License|null $license
5931
		 *
5932
		 * @return bool|\FS_Subscription
5933
		 */
5934
		private function _sync_site_subscription( $license ) {
5935
			if ( ! is_object( $license ) ) {
5936
				unset( $this->_storage->subscription );
5937
5938
				return false;
5939
			}
5940
5941
			// Load subscription details if not lifetime.
5942
			$subscription = $license->is_lifetime() ?
5943
				false :
5944
				$this->_fetch_site_license_subscription();
5945
5946
			if ( is_object( $subscription ) && ! isset( $subscription->error ) ) {
5947
				$this->_storage->subscription = $subscription;
5948
			} else {
5949
				unset( $this->_storage->subscription );
5950
			}
5951
5952
			return $subscription;
5953
		}
5954
5955
		/**
5956
		 * @author Vova Feldman (@svovaf)
5957
		 * @since  1.0.6
5958
		 *
5959
		 * @return bool|\FS_Plugin_License
5960
		 */
5961
		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...
5962
			return $this->_license;
5963
		}
5964
5965
		/**
5966
		 * @return bool|\FS_Subscription
5967
		 */
5968
		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...
5969
			return isset( $this->_storage->subscription ) ?
5970
				$this->_storage->subscription :
5971
				false;
5972
		}
5973
5974
		/**
5975
		 * @author Vova Feldman (@svovaf)
5976
		 * @since  1.0.2
5977
		 *
5978
		 * @param string $plan  Plan name
5979
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
5980
		 *
5981
		 * @return bool
5982
		 */
5983
		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...
5984
			$this->_logger->entrance();
5985
5986
			if ( ! $this->is_registered() ) {
5987
				return false;
5988
			}
5989
5990
			$plan = strtolower( $plan );
5991
5992
			if ( $this->_site->plan->name === $plan ) // Exact plan.
5993
			{
5994
				return true;
5995
			} else if ( $exact ) // Required exact, but plans are different.
5996
			{
5997
				return false;
5998
			}
5999
6000
			$current_plan_order  = - 1;
6001
			$required_plan_order = - 1;
6002
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
6003
				if ( $plan === $this->_plans[ $i ]->name ) {
6004
					$required_plan_order = $i;
6005
				} else if ( $this->_site->plan->name === $this->_plans[ $i ]->name ) {
6006
					$current_plan_order = $i;
6007
				}
6008
			}
6009
6010
			return ( $current_plan_order > $required_plan_order );
6011
		}
6012
6013
		/**
6014
		 * Check if module has only one plan.
6015
		 *
6016
		 * @author Vova Feldman (@svovaf)
6017
		 * @since  1.2.1.7
6018
		 *
6019
		 * @return bool
6020
		 */
6021
		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...
6022
			$this->_logger->entrance();
6023
6024
			if ( ! $this->is_registered() ||
6025
			     ! is_array( $this->_plans ) ||
6026
			     0 === count( $this->_plans )
6027
			) {
6028
				return true;
6029
			}
6030
6031
			return ( 1 === count( $this->_plans ) );
6032
		}
6033
6034
		/**
6035
		 * Check if plan based on trial. If not in trial mode, should return false.
6036
		 *
6037
		 * @since  1.0.9
6038
		 *
6039
		 * @param string $plan  Plan name
6040
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
6041
		 *
6042
		 * @return bool
6043
		 */
6044
		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...
6045
			$this->_logger->entrance();
6046
6047
			if ( ! $this->is_registered() ) {
6048
				return false;
6049
			}
6050
6051
			if ( ! $this->is_trial() ) {
6052
				return false;
6053
			}
6054
6055
			if ( ! isset( $this->_storage->trial_plan ) ) {
6056
				// Store trial plan information.
6057
				$this->_enrich_site_trial_plan( true );
6058
			}
6059
6060
			if ( $this->_storage->trial_plan->name === $plan ) // Exact plan.
6061
			{
6062
				return true;
6063
			} else if ( $exact ) // Required exact, but plans are different.
6064
			{
6065
				return false;
6066
			}
6067
6068
			$current_plan_order  = - 1;
6069
			$required_plan_order = - 1;
6070
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
6071
				if ( $plan === $this->_plans[ $i ]->name ) {
6072
					$required_plan_order = $i;
6073
				} else if ( $this->_storage->trial_plan->name === $this->_plans[ $i ]->name ) {
6074
					$current_plan_order = $i;
6075
				}
6076
			}
6077
6078
			return ( $current_plan_order > $required_plan_order );
6079
		}
6080
6081
		/**
6082
		 * Check if plugin has any paid plans.
6083
		 *
6084
		 * @author Vova Feldman (@svovaf)
6085
		 * @since  1.0.7
6086
		 *
6087
		 * @return bool
6088
		 */
6089
		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...
6090
			return $this->_has_paid_plans ||
6091
			       FS_Plan_Manager::instance()->has_paid_plan( $this->_plans );
6092
		}
6093
6094
		/**
6095
		 * Check if plugin has any plan with a trail.
6096
		 *
6097
		 * @author Vova Feldman (@svovaf)
6098
		 * @since  1.0.9
6099
		 *
6100
		 * @return bool
6101
		 */
6102
		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...
6103
			if ( ! $this->is_registered() ) {
6104
				/**
6105
				 * @author Vova Feldman(@svovaf)
6106
				 * @since  1.2.1.5
6107
				 *
6108
				 * Allow setting a trial from the SDK without calling the API.
6109
				 * But, if the user did opt-in, continue using the real data from the API.
6110
				 */
6111
				if ( $this->_trial_days >= 0 ) {
6112
					return true;
6113
				}
6114
6115
				return false;
6116
			}
6117
6118
			return $this->_storage->get( 'has_trial_plan', false );
6119
		}
6120
6121
		/**
6122
		 * Check if plugin has any free plan, or is it premium only.
6123
		 *
6124
		 * Note: If no plans configured, assume plugin is free.
6125
		 *
6126
		 * @author Vova Feldman (@svovaf)
6127
		 * @since  1.0.7
6128
		 *
6129
		 * @return bool
6130
		 */
6131
		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...
6132
			return ! $this->is_only_premium();
6133
		}
6134
6135
		/**
6136
		 * Displays a license activation dialog box when the user clicks on the "Activate License"
6137
		 * or "Change License" link on the plugins
6138
		 * page.
6139
		 *
6140
		 * @author Leo Fajardo (@leorw)
6141
		 * @since  1.1.9
6142
		 */
6143
		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...
6144
			$vars = array(
6145
				'slug' => $this->_slug,
6146
			);
6147
6148
			fs_require_template( 'forms/license-activation.php', $vars );
6149
			fs_require_template( 'forms/resend-key.php', $vars );
6150
		}
6151
6152
		/**
6153
		 * Displays the opt-out dialog box when the user clicks on the "Opt Out" link on the "Plugins"
6154
		 * page.
6155
		 *
6156
		 * @author Leo Fajardo (@leorw)
6157
		 * @since  1.2.1.5
6158
		 */
6159
		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...
6160
			$vars = array(
6161
				'slug' => $this->_slug,
6162
			);
6163
6164
			fs_require_template( 'forms/optout.php', $vars );
6165
		}
6166
6167
		/**
6168
		 * Prepare page to include all required UI and logic for the license activation dialog.
6169
		 *
6170
		 * @author Vova Feldman (@svovaf)
6171
		 * @since  1.2.0
6172
		 */
6173
		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...
6174
			if ( ! $this->is_user_admin() ) {
6175
				// Only admins can activate a license.
6176
				return;
6177
			}
6178
6179
			if ( ! $this->has_paid_plan() ) {
6180
				// Module doesn't have any paid plans.
6181
				return;
6182
			}
6183
6184
			if ( ! $this->is_premium() ) {
6185
				// Only add license activation logic to the premium version.
6186
				return;
6187
			}
6188
6189
			// Add license activation link and AJAX request handler.
6190
			if ( $this->is_plugins_page() ) {
6191
				/**
6192
				 * @since 1.2.0 Add license action link only on plugins page.
6193
				 */
6194
				$this->_add_license_action_link();
6195
			}
6196
6197
			// Add license activation AJAX callback.
6198
			$this->add_ajax_action( 'activate_license', array( &$this, '_activate_license_ajax_action' ) );
6199
6200
			// Add resend license AJAX callback.
6201
			$this->add_ajax_action( 'resend_license_key', array( &$this, '_resend_license_key_ajax_action' ) );
6202
		}
6203
6204
		/**
6205
		 * @author Leo Fajardo (@leorw)
6206
		 * @since  1.1.9
6207
		 */
6208
		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...
6209
			$this->_logger->entrance();
6210
6211
			$this->check_ajax_referer( 'activate_license' );
6212
6213
			$license_key = trim( fs_request_get( 'license_key' ) );
6214
6215
			if ( empty( $license_key ) ) {
6216
				exit;
6217
			}
6218
6219
			$slug = fs_request_get( 'slug', '', 'post' );
6220
			$fs   = ( $slug === $this->_slug ) ?
6221
				$this :
6222
				$this->get_addon_instance( $slug );
6223
6224
			$error     = false;
6225
			$next_page = false;
6226
6227
			if ( $fs->is_registered() ) {
6228
				$api     = $fs->get_api_site_scope();
6229
				$install = $api->call( '/', 'put', array(
6230
					'license_key' => $fs->apply_filters( 'license_key', $license_key )
6231
				) );
6232
6233
				if ( isset( $install->error ) ) {
6234
					$error = $install->error->message;
6235
				} else {
6236
					$parent_fs = $fs->is_addon() ?
6237
						$fs->get_parent_instance() :
6238
						$fs;
6239
6240
					$next_page = $parent_fs->_get_sync_license_url( $fs->get_id(), true );
6241
6242
					$fs->reconnect_locally();
6243
				}
6244
			} else {
6245
				$next_page = $fs->opt_in( false, false, false, $license_key );
6246
6247
				if ( isset( $next_page->error ) ) {
6248
					$error = $next_page->error;
6249
				}
6250
			}
6251
6252
			$result = array(
6253
				'success' => ( false === $error )
6254
			);
6255
6256
			if ( false !== $error ) {
6257
				$result['error'] = $error;
6258
			} else {
6259
				$result['next_page'] = $next_page;
6260
			}
6261
6262
			echo json_encode( $result );
6263
6264
			exit;
6265
		}
6266
6267
		/**
6268
		 * Billing update AJAX callback.
6269
		 *
6270
		 * @author Vova Feldman (@svovaf)
6271
		 * @since  1.2.1.5
6272
		 */
6273
		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...
6274
			$this->_logger->entrance();
6275
6276
			$this->check_ajax_referer( 'update_billing' );
6277
6278
			if ( ! $this->is_user_admin() ) {
6279
				// Only for admins.
6280
				self::shoot_ajax_failure();
6281
			}
6282
6283
			$billing = fs_request_get( 'billing' );
6284
6285
			$api    = $this->get_api_user_scope();
6286
			$result = $api->call( '/billing.json', 'put', array_merge( $billing, array(
6287
				'plugin_id' => $this->get_parent_id(),
6288
			) ) );
6289
6290
			if ( ! $this->is_api_result_entity( $result ) ) {
6291
				self::shoot_ajax_failure();
6292
			}
6293
6294
			// Purge cached billing.
6295
			$this->get_api_user_scope()->purge_cache( 'billing.json' );
6296
6297
			self::shoot_ajax_success();
6298
		}
6299
6300
		/**
6301
		 * Trial start for anonymous users (AJAX callback).
6302
		 *
6303
		 * @author Vova Feldman (@svovaf)
6304
		 * @since  1.2.1.5
6305
		 */
6306
		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...
6307
			$this->_logger->entrance();
6308
6309
			$this->check_ajax_referer( 'start_trial' );
6310
6311
			if ( ! $this->is_user_admin() ) {
6312
				// Only for admins.
6313
				self::shoot_ajax_failure();
6314
			}
6315
6316
			$trial_data = fs_request_get( 'trial' );
6317
6318
			$next_page = $this->opt_in(
6319
				false,
6320
				false,
6321
				false,
6322
				false,
6323
				false,
6324
				$trial_data['plan_id']
6325
			);
6326
6327
			if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) {
6328
				self::shoot_ajax_failure(
6329
					isset( $next_page->error ) ?
6330
						$next_page->error->message :
6331
						var_export( $next_page, true )
6332
				);
6333
			}
6334
6335
			self::shoot_ajax_success( array(
6336
				'next_page' => $next_page,
6337
			) );
6338
		}
6339
6340
		/**
6341
		 * @author Leo Fajardo (@leorw)
6342
		 * @since  1.2.0
6343
		 */
6344
		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...
6345
			$this->_logger->entrance();
6346
6347
			$this->check_ajax_referer( 'resend_license_key' );
6348
6349
			$email_address = sanitize_email( trim( fs_request_get( 'email', '', 'post' ) ) );
6350
6351
			if ( empty( $email_address ) ) {
6352
				exit;
6353
			}
6354
6355
			$error = false;
6356
6357
			$api    = $this->get_api_plugin_scope();
6358
			$result = $api->call( '/licenses/resend.json', 'post',
6359
				array(
6360
					'email' => $email_address,
6361
					'url'   => home_url(),
6362
				)
6363
			);
6364
6365
			if ( is_object( $result ) && isset( $result->error ) ) {
6366
				$error = $result->error;
6367
6368
				if ( in_array( $error->code, array( 'invalid_email', 'no_user' ) ) ) {
6369
					$error = $this->get_text( 'email-not-found' );
6370
				} else if ( 'no_license' === $error->code ) {
6371
					$error = $this->get_text( 'no-active-licenses' );
6372
				} else {
6373
					$error = $error->message;
6374
				}
6375
			}
6376
6377
			$licenses = array(
6378
				'success' => ( false === $error )
6379
			);
6380
6381
			if ( false !== $error ) {
6382
				$licenses['error'] = sprintf( '%s... %s', $this->get_text( 'oops' ), strtolower( $error ) );
6383
			}
6384
6385
			echo json_encode( $licenses );
6386
6387
			exit;
6388
		}
6389
6390
		/**
6391
		 * Helper method to check if user in the plugins page.
6392
		 *
6393
		 * @author Vova Feldman (@svovaf)
6394
		 * @since  1.2.1.5
6395
		 *
6396
		 * @return bool
6397
		 */
6398
		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...
6399
			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...
6400
6401
			return ( 'plugins.php' === $pagenow );
6402
		}
6403
6404
		/**
6405
		 * Helper method to check if user in the themes page.
6406
		 *
6407
		 * @author Vova Feldman (@svovaf)
6408
		 * @since  1.2.2.6
6409
		 *
6410
		 * @return bool
6411
		 */
6412
		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...
6413
			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...
6414
6415
			return ( 'themes.php' === $pagenow );
6416
		}
6417
6418
		#----------------------------------------------------------------------------------
6419
		#region URL Generators
6420
		#----------------------------------------------------------------------------------
6421
6422
		/**
6423
		 * Alias to pricing_url().
6424
		 *
6425
		 * @author Vova Feldman (@svovaf)
6426
		 * @since  1.0.2
6427
		 *
6428
		 * @uses   pricing_url()
6429
		 *
6430
		 * @param string $period Billing cycle
6431
		 * @param bool   $is_trial
6432
		 *
6433
		 * @return string
6434
		 */
6435
		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...
6436
			return $this->pricing_url( $period, $is_trial );
6437
		}
6438
6439
		/**
6440
		 * @author Vova Feldman (@svovaf)
6441
		 * @since  1.0.9
6442
		 *
6443
		 * @uses   get_upgrade_url()
6444
		 *
6445
		 * @return string
6446
		 */
6447
		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...
6448
			return $this->get_upgrade_url( WP_FS__PERIOD_ANNUALLY, true );
6449
		}
6450
6451
		/**
6452
		 * Plugin's pricing URL.
6453
		 *
6454
		 * @author Vova Feldman (@svovaf)
6455
		 * @since  1.0.4
6456
		 *
6457
		 * @param string $billing_cycle Billing cycle
6458
		 *
6459
		 * @param bool   $is_trial
6460
		 *
6461
		 * @return string
6462
		 */
6463
		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...
6464
			$this->_logger->entrance();
6465
6466
			$params = array(
6467
				'billing_cycle' => $billing_cycle
6468
			);
6469
6470
			if ( $is_trial ) {
6471
				$params['trial'] = 'true';
6472
			}
6473
6474
			return $this->_get_admin_page_url( 'pricing', $params );
6475
		}
6476
6477
		/**
6478
		 * Checkout page URL.
6479
		 *
6480
		 * @author   Vova Feldman (@svovaf)
6481
		 * @since    1.0.6
6482
		 *
6483
		 * @param string $billing_cycle Billing cycle
6484
		 * @param bool   $is_trial
6485
		 * @param array  $extra         (optional) Extra parameters, override other query params.
6486
		 *
6487
		 * @return string
6488
		 */
6489
		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...
6490
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
6491
			$is_trial = false,
6492
			$extra = array()
6493
		) {
6494
			$this->_logger->entrance();
6495
6496
			$params = array(
6497
				'checkout'      => 'true',
6498
				'billing_cycle' => $billing_cycle,
6499
			);
6500
6501
			if ( $is_trial ) {
6502
				$params['trial'] = 'true';
6503
			}
6504
6505
			/**
6506
			 * Params in extra override other params.
6507
			 */
6508
			$params = array_merge( $params, $extra );
6509
6510
			return $this->_get_admin_page_url( 'pricing', $params );
6511
		}
6512
6513
		/**
6514
		 * Add-on checkout URL.
6515
		 *
6516
		 * @author   Vova Feldman (@svovaf)
6517
		 * @since    1.1.7
6518
		 *
6519
		 * @param number $addon_id
6520
		 * @param number $pricing_id
6521
		 * @param string $billing_cycle
6522
		 * @param bool   $is_trial
6523
		 *
6524
		 * @return string
6525
		 */
6526
		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...
6527
			$addon_id,
6528
			$pricing_id,
6529
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
6530
			$is_trial = false
6531
		) {
6532
			return $this->checkout_url( $billing_cycle, $is_trial, array(
6533
				'plugin_id'  => $addon_id,
6534
				'pricing_id' => $pricing_id,
6535
			) );
6536
		}
6537
6538
		#endregion
6539
6540
		#endregion ------------------------------------------------------------------
6541
6542
		/**
6543
		 * Check if plugin has any add-ons.
6544
		 *
6545
		 * @author Vova Feldman (@svovaf)
6546
		 * @since  1.0.5
6547
		 *
6548
		 * @since  1.1.7.3 Base logic only on the parameter provided by the developer in the init function.
6549
		 *
6550
		 * @return bool
6551
		 */
6552
		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...
6553
			$this->_logger->entrance();
6554
6555
			return $this->_has_addons;
6556
		}
6557
6558
		/**
6559
		 * Check if plugin can work in anonymous mode.
6560
		 *
6561
		 * @author     Vova Feldman (@svovaf)
6562
		 * @since      1.0.9
6563
		 *
6564
		 * @return bool
6565
		 *
6566
		 * @deprecated Please use is_enable_anonymous() instead
6567
		 */
6568
		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...
6569
			return $this->_enable_anonymous;
6570
		}
6571
6572
		/**
6573
		 * Check if plugin can work in anonymous mode.
6574
		 *
6575
		 * @author Vova Feldman (@svovaf)
6576
		 * @since  1.1.9
6577
		 *
6578
		 * @return bool
6579
		 */
6580
		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...
6581
			return $this->_enable_anonymous;
6582
		}
6583
6584
		/**
6585
		 * Check if plugin is premium only (no free plans).
6586
		 *
6587
		 * @author Vova Feldman (@svovaf)
6588
		 * @since  1.1.9
6589
		 *
6590
		 * @return bool
6591
		 */
6592
		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...
6593
			return $this->_is_premium_only;
6594
		}
6595
6596
		/**
6597
		 * Checks if the plugin's type is "plugin". The other type is "theme".
6598
		 *
6599
		 * @author Leo Fajardo (@leorw)
6600
		 * @since  1.2.2
6601
		 *
6602
		 * @return bool
6603
		 */
6604
		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...
6605
			return true;
6606
		}
6607
6608
		/**
6609
		 * Check if module has a premium code version.
6610
		 *
6611
		 * Serviceware module might be freemium without any
6612
		 * premium code version, where the paid features
6613
		 * are all part of the service.
6614
		 *
6615
		 * @author Vova Feldman (@svovaf)
6616
		 * @since  1.2.1.6
6617
		 *
6618
		 * @return bool
6619
		 */
6620
		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...
6621
			return $this->_has_premium_version;
6622
		}
6623
6624
		/**
6625
		 * Check if feature supported with current site's plan.
6626
		 *
6627
		 * @author Vova Feldman (@svovaf)
6628
		 * @since  1.0.1
6629
		 *
6630
		 * @todo   IMPLEMENT
6631
		 *
6632
		 * @param number $feature_id
6633
		 *
6634
		 * @throws Exception
6635
		 */
6636
		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...
6637
			throw new Exception( 'not implemented' );
6638
		}
6639
6640
		/**
6641
		 * @author Vova Feldman (@svovaf)
6642
		 * @since  1.0.1
6643
		 *
6644
		 * @return bool Is running in SSL/HTTPS
6645
		 */
6646
		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...
6647
			return WP_FS__IS_HTTPS;
6648
		}
6649
6650
		/**
6651
		 * @author Vova Feldman (@svovaf)
6652
		 * @since  1.0.9
6653
		 *
6654
		 * @return bool Is running in AJAX call.
6655
		 *
6656
		 * @link   http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax
6657
		 */
6658
		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...
6659
			return ( defined( 'DOING_AJAX' ) && DOING_AJAX );
6660
		}
6661
6662
		/**
6663
		 * Check if it's an AJAX call targeted for the current module.
6664
		 *
6665
		 * @author Vova Feldman (@svovaf)
6666
		 * @since  1.2.0
6667
		 *
6668
		 * @param array|string $actions Collection of AJAX actions.
6669
		 *
6670
		 * @return bool
6671
		 */
6672
		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...
6673
			// Verify it's an ajax call.
6674
			if ( ! self::is_ajax() ) {
6675
				return false;
6676
			}
6677
6678
			// Verify the call is relevant for the plugin.
6679
			if ( $this->_slug !== fs_request_get( 'slug' ) ) {
6680
				return false;
6681
			}
6682
6683
			// Verify it's one of the specified actions.
6684
			if ( is_string( $actions ) ) {
6685
				$actions = explode( ',', $actions );
6686
			}
6687
6688
			if ( is_array( $actions ) && 0 < count( $actions ) ) {
6689
				$ajax_action = fs_request_get( 'action' );
6690
6691
				foreach ( $actions as $action ) {
6692
					if ( $ajax_action === $this->get_action_tag( $action ) ) {
6693
						return true;
6694
					}
6695
				}
6696
			}
6697
6698
			return false;
6699
		}
6700
6701
		/**
6702
		 * Check if it's an AJAX call targeted for current request.
6703
		 *
6704
		 * @author Vova Feldman (@svovaf)
6705
		 * @since  1.2.0
6706
		 *
6707
		 * @param array|string $actions Collection of AJAX actions.
6708
		 * @param string       $slug
6709
		 *
6710
		 * @return bool
6711
		 */
6712
		static function is_ajax_action_static( $actions, $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...
6713
			// Verify it's an ajax call.
6714
			if ( ! self::is_ajax() ) {
6715
				return false;
6716
			}
6717
6718
			if ( ! empty( $slug ) ) {
6719
				// Verify the call is relevant for the plugin.
6720
				if ( $slug !== fs_request_get( 'slug' ) ) {
6721
					return false;
6722
				}
6723
			}
6724
6725
			// Verify it's one of the specified actions.
6726
			if ( is_string( $actions ) ) {
6727
				$actions = explode( ',', $actions );
6728
			}
6729
6730
			if ( is_array( $actions ) && 0 < count( $actions ) ) {
6731
				$ajax_action = fs_request_get( 'action' );
6732
6733
				foreach ( $actions as $action ) {
6734
					if ( $ajax_action === self::get_ajax_action_static( $action, $slug ) ) {
6735
						return true;
6736
					}
6737
				}
6738
			}
6739
6740
			return false;
6741
		}
6742
6743
		/**
6744
		 * @author Vova Feldman (@svovaf)
6745
		 * @since  1.1.7
6746
		 *
6747
		 * @return bool
6748
		 */
6749
		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...
6750
			return ( defined( 'DOING_CRON' ) && DOING_CRON );
6751
		}
6752
6753
		/**
6754
		 * Check if a real user is visiting the admin dashboard.
6755
		 *
6756
		 * @author Vova Feldman (@svovaf)
6757
		 * @since  1.1.7
6758
		 *
6759
		 * @return bool
6760
		 */
6761
		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...
6762
			return is_admin() && ! self::is_ajax() && ! $this->is_cron();
6763
		}
6764
6765
		/**
6766
		 * Check if running in HTTPS and if site's plan matching the specified plan.
6767
		 *
6768
		 * @param string $plan
6769
		 * @param bool   $exact
6770
		 *
6771
		 * @return bool
6772
		 */
6773
		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...
6774
			return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) );
6775
		}
6776
6777
		/**
6778
		 * Construct plugin's settings page URL.
6779
		 *
6780
		 * @author Vova Feldman (@svovaf)
6781
		 * @since  1.0.4
6782
		 *
6783
		 * @param string $page
6784
		 * @param array  $params
6785
		 *
6786
		 * @return string
6787
		 */
6788
		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...
6789
			if ( 0 < count( $params ) ) {
6790
				foreach ( $params as $k => $v ) {
6791
					$params[ $k ] = urlencode( $v );
6792
				}
6793
			}
6794
6795
			$page_param = $this->_menu->get_slug( $page );
6796
6797
			if ( ! $this->has_settings_menu() ) {
6798
				if ( ! empty( $page ) ) {
6799
					// Module doesn't have a setting page, but since the request is for
6800
					// a specific Freemius page, use the admin.php path.
6801
					return add_query_arg( array_merge( $params, array(
6802
						'page' => $page_param,
6803
					) ), admin_url( 'admin.php', 'admin' ) );
6804
				} else {
6805
					if ( $this->is_activation_mode() ) {
6806
						/**
6807
						 * @author Vova Feldman
6808
						 * @since  1.2.1.6
6809
						 *
6810
						 * If plugin doesn't have a settings page, create one for the opt-in screen.
6811
						 */
6812
						return add_query_arg( array_merge( $params, array(
6813
							'page' => $this->_slug,
6814
						) ), admin_url( 'admin.php', 'admin' ) );
6815
					} else {
6816
						// Plugin without a settings page.
6817
						return admin_url( 'plugins.php' );
6818
					}
6819
				}
6820
			}
6821
6822
			// Module has a submenu settings page.
6823
			if ( ! $this->_menu->is_top_level() ) {
6824
				$parent_slug = $this->_menu->get_parent_slug();
6825
				$menu_file   = ( false !== strpos( $parent_slug, '.php' ) ) ?
6826
					$parent_slug :
6827
					'admin.php';
6828
6829
				return add_query_arg( array_merge( $params, array(
6830
					'page' => $page_param,
6831
				) ), admin_url( $menu_file, 'admin' ) );
6832
			}
6833
6834
			// Module has a top level CPT settings page.
6835
			if ( $this->_menu->is_cpt() ) {
6836
				if ( empty( $page ) && $this->is_activation_mode() ) {
6837
					return add_query_arg( array_merge( $params, array(
6838
						'page' => $page_param
6839
					) ), admin_url( 'admin.php', 'admin' ) );
6840
				} else {
6841
					if ( ! empty( $page ) ) {
6842
						$params['page'] = $page_param;
6843
					}
6844
6845
					return add_query_arg(
6846
						$params,
6847
						admin_url( $this->_menu->get_raw_slug(), 'admin' )
6848
					);
6849
				}
6850
			}
6851
6852
			// Module has a custom top level settings page.
6853
			return add_query_arg( array_merge( $params, array(
6854
				'page' => $page_param,
6855
			) ), admin_url( 'admin.php', 'admin' ) );
6856
		}
6857
6858
		/**
6859
		 * Plugin's account page + sync license URL.
6860
		 *
6861
		 * @author Vova Feldman (@svovaf)
6862
		 * @since  1.1.9.1
6863
		 *
6864
		 * @param bool|number $plugin_id
6865
		 * @param bool        $add_action_nonce
6866
		 * @param array       $params
6867
		 *
6868
		 * @return string
6869
		 */
6870
		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...
6871
			if ( is_numeric( $plugin_id ) ) {
6872
				$params['plugin_id'] = $plugin_id;
6873
			}
6874
6875
			return $this->get_account_url(
6876
				$this->_slug . '_sync_license',
6877
				$params,
6878
				$add_action_nonce
6879
			);
6880
		}
6881
6882
		/**
6883
		 * Plugin's account URL.
6884
		 *
6885
		 * @author Vova Feldman (@svovaf)
6886
		 * @since  1.0.4
6887
		 *
6888
		 * @param bool|string $action
6889
		 * @param array       $params
6890
		 *
6891
		 * @param bool        $add_action_nonce
6892
		 *
6893
		 * @return string
6894
		 */
6895
		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...
6896
			if ( is_string( $action ) ) {
6897
				$params['fs_action'] = $action;
6898
			}
6899
6900
			self::require_pluggable_essentials();
6901
6902
			return ( $add_action_nonce && is_string( $action ) ) ?
6903
				fs_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) :
6904
				$this->_get_admin_page_url( 'account', $params );
6905
		}
6906
6907
		/**
6908
		 * @author  Vova Feldman (@svovaf)
6909
		 * @since   1.2.0
6910
		 *
6911
		 * @param string $tab
6912
		 * @param bool   $action
6913
		 * @param array  $params
6914
		 * @param bool   $add_action_nonce
6915
		 *
6916
		 * @return string
6917
		 *
6918
		 * @uses    get_account_url()
6919
		 */
6920
		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...
6921
			$params['tab'] = $tab;
6922
6923
			return $this->get_account_url( $action, $params, $add_action_nonce );
6924
		}
6925
6926
		/**
6927
		 * Plugin's account URL.
6928
		 *
6929
		 * @author Vova Feldman (@svovaf)
6930
		 * @since  1.0.4
6931
		 *
6932
		 * @param bool|string $topic
6933
		 * @param bool|string $message
6934
		 *
6935
		 * @return string
6936
		 */
6937
		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...
6938
			$params = array();
6939
			if ( is_string( $topic ) ) {
6940
				$params['topic'] = $topic;
6941
			}
6942
			if ( is_string( $message ) ) {
6943
				$params['message'] = $message;
6944
			}
6945
6946
			if ( $this->is_addon() ) {
6947
				$params['addon_id'] = $this->get_id();
6948
6949
				return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params );
6950
			} else {
6951
				return $this->_get_admin_page_url( 'contact', $params );
6952
			}
6953
		}
6954
6955
		/**
6956
		 * Add-on direct info URL.
6957
		 *
6958
		 * @author Vova Feldman (@svovaf)
6959
		 * @since  1.1.0
6960
		 *
6961
		 * @param string $slug
6962
		 *
6963
		 * @return string
6964
		 */
6965
		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...
6966
			return $this->_get_admin_page_url( 'addons', array(
6967
				'slug' => $slug
6968
			) );
6969
		}
6970
6971
		/* Logger
6972
		------------------------------------------------------------------------------------------------------------------*/
6973
		/**
6974
		 * @param string $id
6975
		 * @param bool   $prefix_slug
6976
		 *
6977
		 * @return FS_Logger
6978
		 */
6979
		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...
6980
			return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id );
6981
		}
6982
6983
		/**
6984
		 * @param      $id
6985
		 * @param bool $load_options
6986
		 * @param bool $prefix_slug
6987
		 *
6988
		 * @return FS_Option_Manager
6989
		 */
6990
		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...
6991
			return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options );
6992
		}
6993
6994
		/* Security
6995
		------------------------------------------------------------------------------------------------------------------*/
6996
		private static function _encrypt( $str ) {
6997
			if ( is_null( $str ) ) {
6998
				return null;
6999
			}
7000
7001
			/**
7002
			 * The encrypt/decrypt functions are used to protect
7003
			 * the user from messing up with some of the sensitive
7004
			 * data stored for the module as a JSON in the database.
7005
			 *
7006
			 * I used the same suggested hack by the theme review team.
7007
			 * For more details, look at the function `Base64UrlDecode()`
7008
			 * in `./sdk/FreemiusBase.php`.
7009
			 *
7010
			 * @todo   Remove this hack once the base64 error is removed from the Theme Check.
7011
			 *
7012
			 * @author Vova Feldman (@svovaf)
7013
			 * @since  1.2.2
7014
			 */
7015
			$fn = 'base64' . '_encode';
7016
7017
			return $fn( $str );
7018
		}
7019
7020
		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...
7021
			if ( is_null( $str ) ) {
7022
				return null;
7023
			}
7024
7025
			/**
7026
			 * The encrypt/decrypt functions are used to protect
7027
			 * the user from messing up with some of the sensitive
7028
			 * data stored for the module as a JSON in the database.
7029
			 *
7030
			 * I used the same suggested hack by the theme review team.
7031
			 * For more details, look at the function `Base64UrlDecode()`
7032
			 * in `./sdk/FreemiusBase.php`.
7033
			 *
7034
			 * @todo   Remove this hack once the base64 error is removed from the Theme Check.
7035
			 *
7036
			 * @author Vova Feldman (@svovaf)
7037
			 * @since  1.2.2
7038
			 */
7039
			$fn = 'base64' . '_decode';
7040
7041
			return $fn( $str );
7042
		}
7043
7044
		/**
7045
		 * @author Vova Feldman (@svovaf)
7046
		 * @since  1.0.5
7047
		 *
7048
		 * @param FS_Entity $entity
7049
		 *
7050
		 * @return FS_Entity Return an encrypted clone entity.
7051
		 */
7052
		private static function _encrypt_entity( FS_Entity $entity ) {
7053
			$clone = clone $entity;
7054
			$props = get_object_vars( $entity );
7055
7056
			foreach ( $props as $key => $val ) {
7057
				$clone->{$key} = self::_encrypt( $val );
7058
			}
7059
7060
			return $clone;
7061
		}
7062
7063
		/**
7064
		 * @author Vova Feldman (@svovaf)
7065
		 * @since  1.0.5
7066
		 *
7067
		 * @param FS_Entity $entity
7068
		 *
7069
		 * @return FS_Entity Return an decrypted clone entity.
7070
		 */
7071
		private static function _decrypt_entity( FS_Entity $entity ) {
7072
			$clone = clone $entity;
7073
			$props = get_object_vars( $entity );
7074
7075
			foreach ( $props as $key => $val ) {
7076
				$clone->{$key} = self::_decrypt( $val );
7077
			}
7078
7079
			return $clone;
7080
		}
7081
7082
		/**
7083
		 * Tries to activate account based on POST params.
7084
		 *
7085
		 * @author Vova Feldman (@svovaf)
7086
		 * @since  1.0.2
7087
		 */
7088
		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...
7089
			if ( $this->is_registered() ) {
7090
				// Already activated.
7091
				return;
7092
			}
7093
7094
			self::_clean_admin_content_section();
7095
7096
			if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) {
7097
//				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...
7098
7099
				// Verify matching plugin details.
7100
				if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) {
7101
					return;
7102
				}
7103
7104
				$user              = new FS_User();
7105
				$user->id          = fs_request_get( 'user_id' );
7106
				$user->public_key  = fs_request_get( 'user_public_key' );
7107
				$user->secret_key  = fs_request_get( 'user_secret_key' );
7108
				$user->email       = fs_request_get( 'user_email' );
7109
				$user->first       = fs_request_get( 'user_first' );
7110
				$user->last        = fs_request_get( 'user_last' );
7111
				$user->is_verified = fs_request_get_bool( 'user_is_verified' );
7112
7113
				$site              = new FS_Site();
7114
				$site->id          = fs_request_get( 'install_id' );
7115
				$site->public_key  = fs_request_get( 'install_public_key' );
7116
				$site->secret_key  = fs_request_get( 'install_secret_key' );
7117
				$site->plan->id    = fs_request_get( 'plan_id' );
7118
				$site->plan->title = fs_request_get( 'plan_title' );
7119
				$site->plan->name  = fs_request_get( 'plan_name' );
7120
7121
				$plans      = array();
7122
				$plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) );
7123
				foreach ( $plans_data as $p ) {
7124
					$plans[] = new FS_Plugin_Plan( $p );
7125
				}
7126
7127
				$this->_set_account( $user, $site, $plans );
7128
7129
				// Reload the page with the keys.
7130
				fs_redirect( $this->_get_admin_page_url() );
7131
			}
7132
		}
7133
7134
		/**
7135
		 * @author Vova Feldman (@svovaf)
7136
		 * @since  1.0.7
7137
		 *
7138
		 * @param string $email
7139
		 *
7140
		 * @return FS_User|bool
7141
		 */
7142
		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...
7143
			self::$_static_logger->entrance();
7144
7145
			$email = trim( strtolower( $email ) );
7146
			$users = self::get_all_users();
7147
			if ( is_array( $users ) ) {
7148
				foreach ( $users as $u ) {
7149
					if ( $email === trim( strtolower( $u->email ) ) ) {
7150
						return $u;
7151
					}
7152
				}
7153
			}
7154
7155
			return false;
7156
		}
7157
7158
		#----------------------------------------------------------------------------------
7159
		#region Account (Loading, Updates & Activation)
7160
		#----------------------------------------------------------------------------------
7161
7162
		/***
7163
		 * Load account information (user + site).
7164
		 *
7165
		 * @author Vova Feldman (@svovaf)
7166
		 * @since  1.0.1
7167
		 */
7168
		private function _load_account() {
7169
			$this->_logger->entrance();
7170
7171
			$this->do_action( 'before_account_load' );
7172
7173
			$sites    = self::get_all_sites();
7174
			$users    = self::get_all_users();
7175
			$plans    = self::get_all_plans();
7176
			$licenses = self::get_all_licenses();
7177
7178
			if ( $this->_logger->is_on() && is_admin() ) {
7179
				$this->_logger->log( 'sites = ' . var_export( $sites, true ) );
7180
				$this->_logger->log( 'users = ' . var_export( $users, true ) );
7181
				$this->_logger->log( 'plans = ' . var_export( $plans, true ) );
7182
				$this->_logger->log( 'licenses = ' . var_export( $licenses, true ) );
7183
			}
7184
7185
			$site = isset( $sites[ $this->_slug ] ) ? $sites[ $this->_slug ] : false;
7186
7187
			if ( is_object( $site ) &&
7188
			     is_numeric( $site->id ) &&
7189
			     is_numeric( $site->user_id ) &&
7190
			     is_object( $site->plan )
7191
			) {
7192
				// Load site.
7193
				$this->_site       = clone $site;
7194
				$this->_site->plan = self::_decrypt_entity( $this->_site->plan );
7195
7196
				// Load relevant user.
7197
				$this->_user = clone $users[ $this->_site->user_id ];
7198
7199
				// Load plans.
7200
				$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...
7201
				if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) {
7202
					$this->_sync_plans();
7203
				} else {
7204
					for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
7205
						if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) {
7206
							$this->_plans[ $i ] = self::_decrypt_entity( $this->_plans[ $i ] );
7207
						} else {
7208
							unset( $this->_plans[ $i ] );
7209
						}
7210
					}
7211
				}
7212
7213
				// Load licenses.
7214
				$this->_licenses = array();
7215
				if ( is_array( $licenses ) &&
7216
				     isset( $licenses[ $this->_slug ] ) &&
7217
				     isset( $licenses[ $this->_slug ][ $this->_user->id ] )
7218
				) {
7219
					$this->_licenses = $licenses[ $this->_slug ][ $this->_user->id ];
7220
				}
7221
7222
				$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...
7223
7224
				if ( $this->_site->version != $this->get_plugin_version() ) {
7225
					// If stored install version is different than current installed plugin version,
7226
					// then update plugin version event.
7227
					$this->update_plugin_version_event();
7228
				}
7229
			}
7230
7231
			$this->_register_account_hooks();
7232
		}
7233
7234
		/**
7235
		 * @author Vova Feldman (@svovaf)
7236
		 * @since  1.0.1
7237
		 *
7238
		 * @param FS_User    $user
7239
		 * @param FS_Site    $site
7240
		 * @param bool|array $plans
7241
		 */
7242
		private function _set_account( FS_User $user, FS_Site $site, $plans = false ) {
7243
			$site->slug    = $this->_slug;
7244
			$site->user_id = $user->id;
7245
7246
			$this->_site = $site;
7247
			$this->_user = $user;
7248
			if ( false !== $plans ) {
7249
				$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...
7250
			}
7251
7252
			$this->send_install_update();
7253
7254
			$this->_store_account();
7255
7256
		}
7257
7258
		/**
7259
		 * @author Vova Feldman (@svovaf)
7260
		 * @since  1.1.7.4
7261
		 *
7262
		 * @param array $override_with
7263
		 *
7264
		 * @return array
7265
		 */
7266
		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...
7267
			$this->_logger->entrance();
7268
7269
			$current_user = self::_get_current_wp_user();
7270
7271
			$activation_action = $this->_slug . '_activate_new';
7272
			$return_url        = $this->is_anonymous() ?
7273
				// If skipped already, then return to the account page.
7274
				$this->get_account_url( $activation_action, array(), false ) :
7275
				// Return to the module's main page.
7276
				$this->_get_admin_page_url(
7277
					'',
7278
					array( 'fs_action' => $activation_action )
7279
				);
7280
7281
			$params = array(
7282
				'user_firstname'               => $current_user->user_firstname,
7283
				'user_lastname'                => $current_user->user_lastname,
7284
				'user_nickname'                => $current_user->user_nicename,
7285
				'user_email'                   => $current_user->user_email,
7286
				'user_ip'                      => WP_FS__REMOTE_ADDR,
7287
				'plugin_slug'                  => $this->_slug,
7288
				'plugin_id'                    => $this->get_id(),
7289
				'plugin_public_key'            => $this->get_public_key(),
7290
				'plugin_version'               => $this->get_plugin_version(),
7291
				'return_url'                   => fs_nonce_url( $return_url, $activation_action ),
7292
				'account_url'                  => fs_nonce_url( $this->_get_admin_page_url(
7293
					'account',
7294
					array( 'fs_action' => 'sync_user' )
7295
				), 'sync_user' ),
7296
				'site_uid'                     => $this->get_anonymous_id(),
7297
				'site_url'                     => get_site_url(),
7298
				'site_name'                    => get_bloginfo( 'name' ),
7299
				'platform_version'             => get_bloginfo( 'version' ),
7300
				'sdk_version'                  => $this->version,
7301
				'programming_language_version' => phpversion(),
7302
				'language'                     => get_bloginfo( 'language' ),
7303
				'charset'                      => get_bloginfo( 'charset' ),
7304
				'is_premium'                   => $this->is_premium(),
7305
				'is_active'                    => true,
7306
				'is_uninstalled'               => false,
7307
			);
7308
7309
			if ( $this->is_pending_activation() &&
7310
			     ! 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...
7311
			) {
7312
				$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...
7313
			}
7314
7315
			if ( WP_FS__SKIP_EMAIL_ACTIVATION && $this->has_secret_key() ) {
7316
				// Even though rand() is known for its security issues,
7317
				// the timestamp adds another layer of protection.
7318
				// It would be very hard for an attacker to get the secret key form here.
7319
				// Plus, this should never run in production since the secret should never
7320
				// be included in the production version.
7321
				$params['ts']     = WP_FS__SCRIPT_START_TIME;
7322
				$params['salt']   = md5( uniqid( rand() ) );
7323
				$params['secure'] = md5(
7324
					$params['ts'] .
7325
					$params['salt'] .
7326
					$this->get_secret_key()
7327
				);
7328
			}
7329
7330
			return array_merge( $params, $override_with );
7331
		}
7332
7333
		/**
7334
		 * 1. If successful opt-in or pending activation returns the next page that the user should be redirected to.
7335
		 * 2. If there was an API error, return the API result.
7336
		 *
7337
		 * @author Vova Feldman (@svovaf)
7338
		 * @since  1.1.7.4
7339
		 *
7340
		 * @param string|bool $email
7341
		 * @param string|bool $first
7342
		 * @param string|bool $last
7343
		 * @param string|bool $license_key
7344
		 * @param bool        $is_uninstall       If "true", this means that the module is currently being uninstalled.
7345
		 *                                        In this case, the user and site info will be sent to the server but no
7346
		 *                                        data will be saved to the WP installation's database.
7347
		 * @param number|bool $trial_plan_id
7348
		 *
7349
		 * @return string|object
7350
		 * @use    WP_Error
7351
		 */
7352
		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...
7353
			$email = false,
7354
			$first = false,
7355
			$last = false,
7356
			$license_key = false,
7357
			$is_uninstall = false,
7358
			$trial_plan_id = false
7359
		) {
7360
			$this->_logger->entrance();
7361
7362
			if ( false === $email ) {
7363
				$current_user = self::_get_current_wp_user();
7364
				$email        = $current_user->user_email;
7365
			}
7366
7367
			/**
7368
			 * @since 1.2.1 If activating with license key, ignore the context-user
7369
			 *              since the user will be automatically loaded from the license.
7370
			 */
7371
			if ( empty( $license_key ) ) {
7372
				// Clean up pending license if opt-ing in again.
7373
				$this->_storage->remove( 'pending_license_key' );
7374
7375
				if ( ! $is_uninstall ) {
7376
					$fs_user = Freemius::_get_user_by_email( $email );
7377
					if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) {
7378
						return $this->install_with_current_user( false, $trial_plan_id );
7379
					}
7380
				}
7381
			}
7382
7383
			$user_info = array();
7384
			if ( ! empty( $email ) ) {
7385
				$user_info['user_email'] = $email;
7386
			}
7387
			if ( ! empty( $first ) ) {
7388
				$user_info['user_firstname'] = $first;
7389
			}
7390
			if ( ! empty( $last ) ) {
7391
				$user_info['user_lastname'] = $last;
7392
			}
7393
7394
			$params = $this->get_opt_in_params( $user_info );
7395
7396
			$filtered_license_key = false;
7397
			if ( is_string( $license_key ) ) {
7398
				$filtered_license_key  = $this->apply_filters( 'license_key', $license_key );
7399
				$params['license_key'] = $filtered_license_key;
7400
			} else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) {
7401
				$params['trial_plan_id'] = $trial_plan_id;
7402
			}
7403
7404
			if ( $is_uninstall ) {
7405
				$params['uninstall_params'] = array(
7406
					'reason_id'   => $this->_storage->uninstall_reason->id,
7407
					'reason_info' => $this->_storage->uninstall_reason->info
7408
				);
7409
			}
7410
7411
			$params['format'] = 'json';
7412
7413
			$url = WP_FS__ADDRESS . '/action/service/user/install/';
7414
			if ( isset( $_COOKIE['XDEBUG_SESSION'] ) ) {
7415
				$url = add_query_arg( 'XDEBUG_SESSION', 'PHPSTORM', $url );
7416
			}
7417
7418
			$response = wp_remote_post( $url, array(
7419
				'method'  => 'POST',
7420
				'body'    => $params,
7421
				'timeout' => 15,
7422
			) );
7423
7424
			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...
7425
				if ( 'https://' === substr( $url, 0, 8 ) &&
7426
				     isset( $response->errors ) &&
7427
				     isset( $response->errors['http_request_failed'] )
7428
				) {
7429
					$http_error = strtolower( $response->errors['http_request_failed'][0] );
7430
7431
					if ( false !== strpos( $http_error, 'ssl' ) ) {
7432
						// Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare).
7433
						$url = 'http://' . substr( $url, 8 );
7434
7435
						$response = wp_remote_post( $url, array(
7436
							'method'  => 'POST',
7437
							'body'    => $params,
7438
							'timeout' => 15,
7439
						) );
7440
					}
7441
				}
7442
			}
7443
7444
			if ( is_wp_error( $response ) ) {
7445
				/**
7446
				 * @var WP_Error $response
7447
				 */
7448
				$result = new stdClass();
7449
7450
				$error_code = $response->get_error_code();
7451
				$error_type = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $error_code ) ) );
7452
7453
				$result->error = (object) array(
7454
					'type'    => $error_type,
7455
					'message' => $response->get_error_message(),
7456
					'code'    => $error_code,
7457
					'http'    => 402
7458
				);
7459
7460
				return $result;
7461
			}
7462
7463
			// Module is being uninstalled, don't handle the returned data.
7464
			if ( $is_uninstall ) {
7465
				return true;
7466
			}
7467
7468
			$decoded = @json_decode( $response['body'] );
7469
7470
			if ( empty( $decoded ) ) {
7471
				return false;
7472
			}
7473
7474
			if ( ! $this->is_api_result_object( $decoded ) ) {
7475
				if ( ! empty( $params['license_key'] ) ) {
7476
					// Pass the fully entered license key to the failure handler.
7477
					$params['license_key'] = $license_key;
7478
				}
7479
7480
				return $is_uninstall ?
7481
					$decoded :
7482
					$this->apply_filters( 'after_install_failure', $decoded, $params );
7483
			} else if ( isset( $decoded->pending_activation ) && $decoded->pending_activation ) {
7484
				// Pending activation, add message.
7485
				return $this->set_pending_confirmation(
7486
					true,
7487
					false,
7488
					$filtered_license_key,
7489
					! empty( $params['trial_plan_id'] )
7490
				);
7491
			} else if ( isset( $decoded->install_secret_key ) ) {
7492
				return $this->install_with_new_user(
7493
					$decoded->user_id,
7494
					$decoded->user_public_key,
7495
					$decoded->user_secret_key,
7496
					$decoded->install_id,
7497
					$decoded->install_public_key,
7498
					$decoded->install_secret_key,
7499
					false
7500
				);
7501
			}
7502
7503
			return $decoded;
7504
		}
7505
7506
		/**
7507
		 * Set user and site identities.
7508
		 *
7509
		 * @author Vova Feldman (@svovaf)
7510
		 * @since  1.0.9
7511
		 *
7512
		 * @param FS_User $user
7513
		 * @param FS_Site $site
7514
		 * @param bool    $redirect
7515
		 * @param bool    $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will
7516
		 *                              redirect (or return a URL) to the account page with a special parameter to
7517
		 *                              trigger the auto installation processes.
7518
		 *
7519
		 * @return string If redirect is `false`, returns the next page the user should be redirected to.
7520
		 */
7521
		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...
7522
			FS_User $user,
7523
			FS_Site $site,
7524
			$redirect = true,
7525
			$auto_install = false
7526
		) {
7527
			$this->_user = $user;
7528
			$this->_site = $site;
7529
7530
			$this->_sync_plans();
7531
7532
			$this->_enrich_site_plan( false );
7533
7534
			$this->_set_account( $user, $site );
7535
7536
			if ( $this->is_trial() ) {
7537
				// Store trial plan information.
7538
				$this->_enrich_site_trial_plan( true );
7539
			}
7540
7541
			// If Freemius was OFF before, turn it on.
7542
			$this->turn_on();
7543
7544
			$this->do_action( 'after_account_connection', $user, $site );
7545
7546
			if ( is_numeric( $site->license_id ) ) {
7547
				$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...
7548
			}
7549
7550
			$this->_admin_notices->remove_sticky( 'connect_account' );
7551
7552
			if ( $this->is_pending_activation() ) {
7553
				// Remove pending activation sticky notice (if still exist).
7554
				$this->_admin_notices->remove_sticky( 'activation_pending' );
7555
7556
				// Remove plugin from pending activation mode.
7557
				unset( $this->_storage->is_pending_activation );
7558
7559
				if ( ! $this->is_paying_or_trial() ) {
7560
					$this->_admin_notices->add_sticky(
7561
						sprintf( $this->get_text( 'plugin-x-activation-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
7562
						'activation_complete'
7563
					);
7564
				}
7565
			}
7566
7567
			if ( $this->is_paying_or_trial() ) {
7568
				if ( ! $this->is_premium() || ! $this->has_premium_version() ) {
7569
					if ( $this->is_paying() ) {
7570
						$this->_admin_notices->add_sticky(
7571
							sprintf(
7572
								$this->get_text( 'activation-with-plan-x-message' ),
7573
								$this->_site->plan->title
7574
							) . $this->get_complete_upgrade_instructions(),
7575
							'plan_upgraded',
7576
							$this->get_text( 'yee-haw' ) . '!'
7577
						);
7578
					} else {
7579
						$this->_admin_notices->add_sticky(
7580
							sprintf(
7581
								$this->get_text( 'trial-started-message' ),
7582
								'<i>' . $this->get_plugin_name() . '</i>'
7583
							) . $this->get_complete_upgrade_instructions( $this->_storage->trial_plan->title ),
7584
							'trial_started',
7585
							$this->get_text( 'yee-haw' ) . '!'
7586
						);
7587
					}
7588
				}
7589
7590
				$this->_admin_notices->remove_sticky( array(
7591
					'trial_promotion',
7592
				) );
7593
			}
7594
7595
			$plugin_id = fs_request_get( 'plugin_id', false );
7596
7597
			// Store activation time ONLY for plugins (not add-ons).
7598
			if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) {
7599
				$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
7600
			}
7601
7602
			$next_page = '';
7603
7604
			$extra = array();
7605
			if ( $auto_install ) {
7606
				$extra['auto_install'] = 'true';
7607
			}
7608
7609
			if ( is_numeric( $plugin_id ) ) {
7610
				/**
7611
				 * @author Leo Fajardo
7612
				 * @since  1.2.1.6
7613
				 *
7614
				 * Also sync the license after an anonymous user subscribes.
7615
				 */
7616
				if ( $this->is_anonymous() || $plugin_id != $this->_plugin->id ) {
7617
					// Add-on was installed - sync license right after install.
7618
					$next_page = $this->_get_sync_license_url( $plugin_id, true, $extra );
7619
				}
7620
			} else {
7621
				/**
7622
				 * @author Vova Feldman (@svovaf)
7623
				 * @since  1.1.9 If site installed with a valid license, sync license.
7624
				 */
7625
				if ( $this->is_paying() ) {
7626
					$this->_sync_plugin_license( true );
7627
				}
7628
7629
				// Reload the page with the keys.
7630
				$next_page = $this->is_anonymous() ?
7631
					// If user previously skipped, redirect to account page.
7632
					$this->get_account_url( false, $extra ) :
7633
					$this->get_after_activation_url( 'after_connect_url' );
7634
			}
7635
7636
			if ( ! empty( $next_page ) && $redirect ) {
7637
				fs_redirect( $next_page );
7638
			}
7639
7640
			return $next_page;
7641
		}
7642
7643
		/**
7644
		 * Install plugin with new user information after approval.
7645
		 *
7646
		 * @author Vova Feldman (@svovaf)
7647
		 * @since  1.0.7
7648
		 */
7649
		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...
7650
			$this->_logger->entrance();
7651
7652
			if ( $this->is_registered() ) {
7653
				return;
7654
			}
7655
7656
			if ( fs_request_is_action( $this->_slug . '_activate_new' ) ) {
7657
//				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...
7658
7659
				if ( fs_request_has( 'user_secret_key' ) ) {
7660
					$this->install_with_new_user(
7661
						fs_request_get( 'user_id' ),
7662
						fs_request_get( 'user_public_key' ),
7663
						fs_request_get( 'user_secret_key' ),
7664
						fs_request_get( 'install_id' ),
7665
						fs_request_get( 'install_public_key' ),
7666
						fs_request_get( 'install_secret_key' ),
7667
						true,
7668
						fs_request_get_bool( 'auto_install' )
7669
					);
7670
				} else if ( fs_request_has( 'pending_activation' ) ) {
7671
					$this->set_pending_confirmation( fs_request_get( 'user_email' ), true );
7672
				}
7673
			}
7674
		}
7675
7676
		/**
7677
		 * Install plugin with new user.
7678
		 *
7679
		 * @author Vova Feldman (@svovaf)
7680
		 * @since  1.1.7.4
7681
		 *
7682
		 * @param number $user_id
7683
		 * @param string $user_public_key
7684
		 * @param string $user_secret_key
7685
		 * @param number $install_id
7686
		 * @param string $install_public_key
7687
		 * @param string $install_secret_key
7688
		 * @param bool   $redirect
7689
		 * @param bool   $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will
7690
		 *                             redirect (or return a URL) to the account page with a special parameter to
7691
		 *                             trigger the auto installation processes.
7692
		 *
7693
		 * @return string If redirect is `false`, returns the next page the user should be redirected to.
7694
		 */
7695
		private function install_with_new_user(
7696
			$user_id,
7697
			$user_public_key,
7698
			$user_secret_key,
7699
			$install_id,
7700
			$install_public_key,
7701
			$install_secret_key,
7702
			$redirect = true,
7703
			$auto_install = false
7704
		) {
7705
			$user             = new FS_User();
7706
			$user->id         = $user_id;
7707
			$user->public_key = $user_public_key;
7708
			$user->secret_key = $user_secret_key;
7709
7710
			$this->_user = $user;
7711
			$user_result = $this->get_api_user_scope()->get();
7712
			$user        = new FS_User( $user_result );
7713
			$this->_user = $user;
7714
7715
			$site             = new FS_Site();
7716
			$site->id         = $install_id;
7717
			$site->public_key = $install_public_key;
7718
			$site->secret_key = $install_secret_key;
7719
7720
			$this->_site = $site;
7721
			$site_result = $this->get_api_site_scope()->get();
7722
			$site        = new FS_Site( $site_result );
7723
			$this->_site = $site;
7724
7725
			return $this->setup_account(
7726
				$this->_user,
7727
				$this->_site,
7728
				$redirect,
7729
				$auto_install
7730
			);
7731
		}
7732
7733
		/**
7734
		 * @author Vova Feldman (@svovaf)
7735
		 * @since  1.1.7.4
7736
		 *
7737
		 * @param string|bool $email
7738
		 * @param bool        $redirect
7739
		 * @param string|bool $license_key      Since 1.2.1.5
7740
		 * @param bool        $is_pending_trial Since 1.2.1.5
7741
		 *
7742
		 * @return string Since 1.2.1.5 if $redirect is `false`, return the pending activation page.
7743
		 */
7744
		private function set_pending_confirmation(
7745
			$email = false,
7746
			$redirect = true,
7747
			$license_key = false,
7748
			$is_pending_trial = false
7749
		) {
7750
			if ( $this->_ignore_pending_mode ) {
7751
				/**
7752
				 * If explicitly asked to ignore pending mode, set to anonymous mode
7753
				 * if require confirmation before finalizing the opt-in.
7754
				 *
7755
				 * @author Vova Feldman
7756
				 * @since  1.2.1.6
7757
				 */
7758
				$this->skip_connection();
7759
			} else {
7760
				// Install must be activated via email since
7761
				// user with the same email already exist.
7762
				$this->_storage->is_pending_activation = true;
7763
				$this->_add_pending_activation_notice( $email, $is_pending_trial );
7764
			}
7765
7766
			if ( ! empty( $license_key ) ) {
7767
				$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...
7768
			}
7769
7770
			// Remove the opt-in sticky notice.
7771
			$this->_admin_notices->remove_sticky( array(
7772
				'connect_account',
7773
				'trial_promotion',
7774
			) );
7775
7776
			$next_page = $this->get_after_activation_url( 'after_pending_connect_url' );
7777
7778
			// Reload the page with with pending activation message.
7779
			if ( $redirect ) {
7780
				fs_redirect( $next_page );
7781
			}
7782
7783
			return $next_page;
7784
		}
7785
7786
		/**
7787
		 * Install plugin with current logged WP user info.
7788
		 *
7789
		 * @author Vova Feldman (@svovaf)
7790
		 * @since  1.0.7
7791
		 */
7792
		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...
7793
			$this->_logger->entrance();
7794
7795
			if ( $this->is_registered() ) {
7796
				return;
7797
			}
7798
7799
			if ( fs_request_is_action( $this->_slug . '_activate_existing' ) && fs_request_is_post() ) {
7800
//				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...
7801
7802
				/**
7803
				 * @author Vova Feldman (@svovaf)
7804
				 * @since  1.1.9 Add license key if given.
7805
				 */
7806
				$license_key = fs_request_get( 'license_secret_key' );
7807
7808
				$this->install_with_current_user( $license_key );
7809
			}
7810
		}
7811
7812
7813
		/**
7814
		 * @author Vova Feldman (@svovaf)
7815
		 * @since  1.1.7.4
7816
		 *
7817
		 * @param string|bool $license_key
7818
		 * @param number|bool $trial_plan_id
7819
		 * @param bool        $redirect
7820
		 *
7821
		 * @return string|object If redirect is `false`, returns the next page the user should be redirected to, or the
7822
		 *                       API error object if failed to install.
7823
		 */
7824
		private function install_with_current_user(
7825
			$license_key = false,
7826
			$trial_plan_id = false,
7827
			$redirect = true
7828
		) {
7829
			// Get current logged WP user.
7830
			$current_user = self::_get_current_wp_user();
7831
7832
			// Find the relevant FS user by the email.
7833
			$user = self::_get_user_by_email( $current_user->user_email );
7834
7835
			// We have to set the user before getting user scope API handler.
7836
			$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...
7837
7838
			$extra_install_params = array(
7839
				'uid' => $this->get_anonymous_id(),
7840
			);
7841
7842
			if ( ! empty( $license_key ) ) {
7843
				$filtered_license_key                = $this->apply_filters( 'license_key', $license_key );
7844
				$extra_install_params['license_key'] = $filtered_license_key;
7845
			} else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) {
7846
				$extra_install_params['trial_plan_id'] = $trial_plan_id;
7847
			}
7848
7849
			$args = $this->get_install_data_for_api( $extra_install_params, false, false );
7850
7851
			// Install the plugin.
7852
			$install = $this->get_api_user_scope()->call(
7853
				"/plugins/{$this->get_id()}/installs.json",
7854
				'post',
7855
				$args
7856
			);
7857
7858
			if ( ! $this->is_api_result_entity( $install ) ) {
7859
				if ( ! empty( $args['license_key'] ) ) {
7860
					// Pass full the fully entered license key to the failure handler.
7861
					$args['license_key'] = $license_key;
7862
				}
7863
7864
				$install = $this->apply_filters( 'after_install_failure', $install, $args );
7865
7866
				$this->_admin_notices->add(
7867
					sprintf( $this->get_text( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
7868
					$this->get_text( 'contact-us-with-error-message' ) . ' ' . '<b>' . $install->error->message . '</b>',
7869
					$this->get_text( 'oops' ) . '...',
7870
					'error'
7871
				);
7872
7873
				if ( $redirect ) {
7874
					fs_redirect( $this->get_activation_url( array( 'error' => $install->error->message ) ) );
7875
				}
7876
7877
				return $install;
7878
			}
7879
7880
			$site        = new FS_Site( $install );
7881
			$this->_site = $site;
7882
7883
			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...
7884
		}
7885
7886
		/**
7887
		 * Tries to activate add-on account based on parent plugin info.
7888
		 *
7889
		 * @author Vova Feldman (@svovaf)
7890
		 * @since  1.0.6
7891
		 *
7892
		 * @param Freemius $parent_fs
7893
		 */
7894
		private function _activate_addon_account( Freemius $parent_fs ) {
7895
			if ( $this->is_registered() ) {
7896
				// Already activated.
7897
				return;
7898
			}
7899
7900
			// Activate add-on with parent plugin credentials.
7901
			$addon_install = $parent_fs->get_api_site_scope()->call(
7902
				"/addons/{$this->_plugin->id}/installs.json",
7903
				'post',
7904
				$this->get_install_data_for_api( array(
7905
					'uid' => $this->get_anonymous_id(),
7906
				), false, false )
7907
			);
7908
7909
			if ( isset( $addon_install->error ) ) {
7910
				$this->_admin_notices->add(
7911
					sprintf( $this->get_text( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
7912
					$this->get_text( 'contact-us-with-error-message' ) . ' ' . '<b>' . $addon_install->error->message . '</b>',
7913
					$this->get_text( 'oops' ) . '...',
7914
					'error'
7915
				);
7916
7917
				return;
7918
			}
7919
7920
			// First of all, set site info - otherwise we won't
7921
			// be able to invoke API calls.
7922
			$this->_site = new FS_Site( $addon_install );
7923
7924
			// Sync add-on plans.
7925
			$this->_sync_plans();
7926
7927
			// Get site's current plan.
7928
			$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...
7929
7930
			// Get user information based on parent's plugin.
7931
			$user = $parent_fs->get_user();
7932
7933
			$this->_set_account( $user, $this->_site );
7934
7935
			// Sync licenses.
7936
			$this->_sync_licenses();
7937
7938
			// Try to activate premium license.
7939
			$this->_activate_license( true );
7940
		}
7941
7942
		#endregion
7943
7944
		#----------------------------------------------------------------------------------
7945
		#region Admin Menu Items
7946
		#----------------------------------------------------------------------------------
7947
7948
		private $_menu_items = array();
7949
7950
		/**
7951
		 * @author Vova Feldman (@svovaf)
7952
		 * @since  1.0.7
7953
		 *
7954
		 * @return string
7955
		 */
7956
		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...
7957
			return $this->_menu->get_slug();
7958
		}
7959
7960
		/**
7961
		 * @author Vova Feldman (@svovaf)
7962
		 * @since  1.0.9
7963
		 */
7964
		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...
7965
//			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...
7966
//				return;
7967
//			}
7968
7969
			if ( ! $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) {
7970
				$this->_menu->remove_menu_item();
7971
			} else {
7972
				$this->do_action( 'before_admin_menu_init' );
7973
7974
				$this->add_menu_action();
7975
				$this->add_submenu_items();
7976
			}
7977
		}
7978
7979
		/**
7980
		 * Admin dashboard menu items modifications.
7981
		 *
7982
		 * NOTE: admin_menu action executed before admin_init.
7983
		 *
7984
		 * @author Vova Feldman (@svovaf)
7985
		 * @since  1.0.7
7986
		 *
7987
		 */
7988
		private function add_menu_action() {
7989
			if ( $this->is_activation_mode() ) {
7990
				$this->override_plugin_menu_with_activation();
7991
			} else {
7992
				// If not registered try to install user.
7993
				if ( ! $this->is_registered() &&
7994
				     fs_request_is_action( $this->_slug . '_activate_new' )
7995
				) {
7996
					$this->_install_with_new_user();
7997
				} else if ( fs_request_is_action( 'sync_user' ) && ! $this->has_settings_menu() ) {
7998
					$this->_handle_account_user_sync();
7999
				}
8000
			}
8001
		}
8002
8003
		/**
8004
		 * @author Vova Feldman (@svovaf)
8005
		 * @since  1.0.1
8006
		 */
8007
		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...
8008
			$this->_logger->entrance();
8009
8010
			$page = strtolower( isset( $_REQUEST['page'] ) ? $_REQUEST['page'] : '' );
8011
8012
			$this->_logger->log( 'page = ' . $page );
8013
8014
			foreach ( $this->_menu_items as $priority => $items ) {
8015
				foreach ( $items as $item ) {
8016
					if ( isset( $item['url'] ) ) {
8017
						if ( $page === $this->_menu->get_slug( strtolower( $item['menu_slug'] ) ) ) {
8018
							$this->_logger->log( 'Redirecting to ' . $item['url'] );
8019
8020
							fs_redirect( $item['url'] );
8021
						}
8022
					}
8023
				}
8024
			}
8025
		}
8026
8027
		/**
8028
		 * Remove plugin's all admin menu items & pages, and replace with activation page.
8029
		 *
8030
		 * @author Vova Feldman (@svovaf)
8031
		 * @since  1.0.1
8032
		 */
8033
		private function override_plugin_menu_with_activation() {
0 ignored issues
show
Coding Style introduced by
override_plugin_menu_with_activation 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...
8034
			$this->_logger->entrance();
8035
8036
			$hook = false;
8037
8038
			if ( ! $this->_menu->has_menu() ) {
8039
				// Add the opt-in page without a menu item.
8040
				$hook = FS_Admin_Menu_Manager::add_subpage(
8041
					null,
8042
					$this->get_plugin_name(),
8043
					$this->get_plugin_name(),
8044
					'manage_options',
8045
					$this->_slug,
8046
					array( &$this, '_connect_page_render' )
8047
				);
8048
			} else if ( $this->_menu->is_top_level() ) {
8049
				$hook = $this->_menu->override_menu_item( array( &$this, '_connect_page_render' ) );
8050
8051
				if ( false === $hook ) {
8052
					// Create new menu item just for the opt-in.
8053
					$hook = FS_Admin_Menu_Manager::add_page(
8054
						$this->get_plugin_name(),
8055
						$this->get_plugin_name(),
8056
						'manage_options',
8057
						$this->_menu->get_slug(),
8058
						array( &$this, '_connect_page_render' )
8059
					);
8060
				}
8061
			} else {
8062
				$menus = array( $this->_menu->get_parent_slug() );
8063
8064
				if ( $this->_menu->is_override_exact() ) {
8065
					if ( ! $this->is_matching_url( $_SERVER['REQUEST_URI'], $this->get_activation_url() ) ) {
8066
						return;
8067
					}
8068
				}
8069
8070
				foreach ( $menus as $parent_slug ) {
8071
					$hook = $this->_menu->override_submenu_action(
8072
						$parent_slug,
8073
						$this->_menu->get_raw_slug(),
8074
						array( &$this, '_connect_page_render' )
8075
					);
8076
8077
					if ( false !== $hook ) {
8078
						// Found plugin's submenu item.
8079
						break;
8080
					}
8081
				}
8082
			}
8083
8084
			if ( $this->is_activation_page() ) {
8085
				// Clean admin page from distracting content.
8086
				self::_clean_admin_content_section();
8087
			}
8088
8089
			if ( false !== $hook ) {
8090
				if ( fs_request_is_action( $this->_slug . '_activate_existing' ) ) {
8091
					add_action( "load-$hook", array( &$this, '_install_with_current_user' ) );
8092
				} else if ( fs_request_is_action( $this->_slug . '_activate_new' ) ) {
8093
					add_action( "load-$hook", array( &$this, '_install_with_new_user' ) );
8094
				}
8095
			}
8096
		}
8097
8098
		/**
8099
		 * @author Leo Fajardo (leorw)
8100
		 * @since  1.2.1
8101
		 *
8102
		 * return string
8103
		 */
8104
		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...
8105
			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...
8106
8107
			$top_level_menu_slug = $this->get_top_level_menu_slug();
8108
8109
			foreach ( $menu as $menu_info ) {
8110
				/**
8111
				 * The second element in the menu info array is the capability/role that has access to the menu and the
8112
				 * third element is the menu slug.
8113
				 */
8114
				if ( $menu_info[2] === $top_level_menu_slug ) {
8115
					return $menu_info[1];
8116
				}
8117
			}
8118
8119
			return 'read';
8120
		}
8121
8122
		/**
8123
		 * @author Vova Feldman (@svovaf)
8124
		 * @since  1.0.0
8125
		 *
8126
		 * @return string
8127
		 */
8128
		private function get_top_level_menu_slug() {
8129
			return ( $this->is_addon() ?
8130
				$this->get_parent_instance()->_menu->get_top_level_menu_slug() :
8131
				$this->_menu->get_top_level_menu_slug() );
8132
		}
8133
8134
		/**
8135
		 * Add default Freemius menu items.
8136
		 *
8137
		 * @author Vova Feldman (@svovaf)
8138
		 * @since  1.0.0
8139
		 */
8140
		private function add_submenu_items() {
8141
			$this->_logger->entrance();
8142
8143
			if ( ! $this->is_addon() ) {
8144
				if ( ! $this->is_activation_mode() ) {
8145
					if ( $this->is_registered() ) {
8146
						// Add user account page.
8147
						$this->add_submenu_item(
8148
							$this->get_text( 'account' ),
8149
							array( &$this, '_account_page_render' ),
8150
							$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'account' ),
8151
							'manage_options',
8152
							'account',
8153
							array( &$this, '_account_page_load' ),
8154
							WP_FS__DEFAULT_PRIORITY,
8155
							$this->is_submenu_item_visible( 'account' )
8156
						);
8157
					}
8158
8159
					// Add contact page.
8160
					$this->add_submenu_item(
8161
						$this->get_text( 'contact-us' ),
8162
						array( &$this, '_contact_page_render' ),
8163
						$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'contact-us' ),
8164
						'manage_options',
8165
						'contact',
8166
						'Freemius::_clean_admin_content_section',
8167
						WP_FS__DEFAULT_PRIORITY,
8168
						$this->is_submenu_item_visible( 'contact' )
8169
					);
8170
8171
					if ( $this->has_addons() ) {
8172
						$this->add_submenu_item(
8173
							$this->get_text( 'add-ons' ),
8174
							array( &$this, '_addons_page_render' ),
8175
							$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'add-ons' ),
8176
							'manage_options',
8177
							'addons',
8178
							array( &$this, '_addons_page_load' ),
8179
							WP_FS__LOWEST_PRIORITY - 1,
8180
							$this->is_submenu_item_visible( 'addons' )
8181
						);
8182
					}
8183
8184
					$show_pricing = (
8185
						// Has at least one paid plan.
8186
						$this->has_paid_plan() &&
8187
						// Didn't ask to hide the pricing page.
8188
						$this->is_submenu_item_visible( 'pricing' ) &&
8189
						// Don't have a valid active license or has more than one plan.
8190
						( ! $this->is_paying() || ! $this->is_single_plan() )
8191
					);
8192
					// If user don't have paid plans, add pricing page
8193
					// to support add-ons checkout but don't add the submenu item.
8194
					// || (isset( $_GET['page'] ) && $this->_menu->get_slug( 'pricing' ) == $_GET['page']);
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% 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...
8195
8196
					$pricing_cta_slug = 'upgrade';
8197
					$pricing_class    = 'upgrade-mode';
8198
					if ( $show_pricing ) {
8199
						if ( $this->_admin_notices->has_sticky( 'trial_promotion' ) &&
8200
						     ! $this->is_paying_or_trial()
8201
						) {
8202
							// If running a trial promotion, modify the pricing to load the trial.
8203
							$pricing_cta_slug = 'start-trial';
8204
							$pricing_class    = 'trial-mode';
8205
						} else if ( $this->is_paying() ) {
8206
							$pricing_cta_slug = 'pricing';
8207
							$pricing_class    = '';
8208
						}
8209
					}
8210
8211
					// Add upgrade/pricing page.
8212
					$this->add_submenu_item(
8213
						$this->get_text( $pricing_cta_slug ) . '&nbsp;&nbsp;' . ( is_rtl() ? '&#x2190;' : '&#x27a4;' ),
8214
						array( &$this, '_pricing_page_render' ),
8215
						$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'pricing' ),
8216
						'manage_options',
8217
						'pricing',
8218
						'Freemius::_clean_admin_content_section',
8219
						WP_FS__LOWEST_PRIORITY,
8220
						$show_pricing,
8221
						$pricing_class
8222
					);
8223
				}
8224
			}
8225
8226
8227
			if ( 0 < count( $this->_menu_items ) ) {
8228
				if ( ! $this->_menu->is_top_level() ) {
8229
					fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
8230
8231
					// Append submenu items right after the plugin's submenu item.
8232
					$this->order_sub_submenu_items();
8233
				} else {
8234
					// Append submenu items.
8235
					$this->embed_submenu_items();
8236
				}
8237
			}
8238
		}
8239
8240
		/**
8241
		 * Moved the actual submenu item additions to a separated function,
8242
		 * in order to support sub-submenu items when the plugin's settings
8243
		 * only have a submenu and not top-level menu item.
8244
		 *
8245
		 * @author Vova Feldman (@svovaf)
8246
		 * @since  1.1.4
8247
		 */
8248
		private function embed_submenu_items() {
8249
			$item_template = $this->_menu->is_top_level() ?
8250
				'<span class="fs-submenu-item %s %s %s">%s</span>' :
8251
				'<span class="fs-submenu-item fs-sub %s %s %s">%s</span>';
8252
8253
			$top_level_menu_capability = $this->get_top_level_menu_capability();
8254
8255
			ksort( $this->_menu_items );
8256
8257
			foreach ( $this->_menu_items as $priority => $items ) {
8258
				foreach ( $items as $item ) {
8259
					$capability = ( ! empty( $item['capability'] ) ? $item['capability'] : $top_level_menu_capability );
8260
8261
					$menu_item = sprintf(
8262
						$item_template,
8263
						$this->_slug,
8264
						$item['menu_slug'],
8265
						! empty( $item['class'] ) ? $item['class'] : '',
8266
						$item['menu_title']
8267
					);
8268
8269
					$menu_slug = $this->_menu->get_slug( $item['menu_slug'] );
8270
8271
					if ( ! isset( $item['url'] ) ) {
8272
						$hook = FS_Admin_Menu_Manager::add_subpage(
8273
							$item['show_submenu'] ?
8274
								$this->get_top_level_menu_slug() :
8275
								null,
8276
							$item['page_title'],
8277
							$menu_item,
8278
							$capability,
8279
							$menu_slug,
8280
							$item['render_function']
8281
						);
8282
8283
						if ( false !== $item['before_render_function'] ) {
8284
							add_action( "load-$hook", $item['before_render_function'] );
8285
						}
8286
					} else {
8287
						FS_Admin_Menu_Manager::add_subpage(
8288
							$this->get_top_level_menu_slug(),
8289
							$item['page_title'],
8290
							$menu_item,
8291
							$capability,
8292
							$menu_slug,
8293
							array( $this, '' )
8294
						);
8295
					}
8296
				}
8297
			}
8298
		}
8299
8300
		/**
8301
		 * Re-order the submenu items so all Freemius added new submenu items
8302
		 * are added right after the plugin's settings submenu item.
8303
		 *
8304
		 * @author Vova Feldman (@svovaf)
8305
		 * @since  1.1.4
8306
		 */
8307
		private function order_sub_submenu_items() {
8308
			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...
8309
8310
			$menu_slug = $this->_menu->get_top_level_menu_slug();
8311
8312
			/**
8313
			 * Before "admin_menu" fires, WordPress will loop over the default submenus and remove pages for which the user
8314
			 * does not have permissions. So in case a plugin does not have top-level menu but does have submenus under any
8315
			 * of the default menus, only users that have the right role can access its sub-submenus (Account, Contact Us,
8316
			 * Support Forum, etc.) since $submenu[ $menu_slug ] will be empty if the user doesn't have permission.
8317
			 *
8318
			 * In case a plugin does not have submenus under any of the default menus but does have submenus under the menu
8319
			 * of another plugin, only users that have the right role can access its sub-submenus since we will use the
8320
			 * capability needed to access the parent menu as the capability for the submenus that we will add.
8321
			 */
8322
			if ( empty( $submenu[ $menu_slug ] ) ) {
8323
				return;
8324
			}
8325
8326
			$top_level_menu = &$submenu[ $menu_slug ];
8327
8328
			$all_submenu_items_after = array();
8329
8330
			$found_submenu_item = false;
8331
8332
			foreach ( $top_level_menu as $submenu_id => $meta ) {
8333
				if ( $found_submenu_item ) {
8334
					// Remove all submenu items after the plugin's submenu item.
8335
					$all_submenu_items_after[] = $meta;
8336
					unset( $top_level_menu[ $submenu_id ] );
8337
				}
8338
8339
				if ( $this->_menu->get_raw_slug() === $meta[2] ) {
8340
					// Found the submenu item, put all below.
8341
					$found_submenu_item = true;
8342
					continue;
8343
				}
8344
			}
8345
8346
			// Embed all plugin's new submenu items.
8347
			$this->embed_submenu_items();
8348
8349
			// Start with specially high number to make sure it's appended.
8350
			$i = max( 10000, max( array_keys( $top_level_menu ) ) + 1 );
8351
			foreach ( $all_submenu_items_after as $meta ) {
8352
				$top_level_menu[ $i ] = $meta;
8353
				$i ++;
8354
			}
8355
8356
			// Sort submenu items.
8357
			ksort( $top_level_menu );
8358
		}
8359
8360
		/**
8361
		 * Displays the Support Forum link when enabled.
8362
		 *
8363
		 * Can be filtered like so:
8364
		 *
8365
		 *  function _fs_show_support_menu( $is_visible, $menu_id ) {
8366
		 *      if ( 'support' === $menu_id ) {
8367
		 *            return _fs->is_registered();
8368
		 *        }
8369
		 *        return $is_visible;
8370
		 *    }
8371
		 *    _fs()->add_filter('is_submenu_visible', '_fs_show_support_menu', 10, 2);
8372
		 *
8373
		 */
8374
		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...
8375
			if ( ! $this->is_on() ) {
8376
				return;
8377
			}
8378
8379
			if ( ! $this->is_activation_mode() ) {
8380
				if ( $this->is_submenu_item_visible( 'support' ) ) {
8381
					$this->add_submenu_link_item(
8382
						$this->apply_filters( 'support_forum_submenu', $this->get_text( 'support-forum' ) ),
8383
						$this->apply_filters( 'support_forum_url', 'https://wordpress.org/support/plugin/' . $this->_slug ),
8384
						'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...
8385
						null,
8386
						50
8387
					);
8388
				}
8389
			}
8390
		}
8391
8392
		/**
8393
		 * @author Vova Feldman (@svovaf)
8394
		 * @since  1.0.1
8395
		 *
8396
		 * @param string        $menu_title
8397
		 * @param callable      $render_function
8398
		 * @param bool|string   $page_title
8399
		 * @param string        $capability
8400
		 * @param bool|string   $menu_slug
8401
		 * @param bool|callable $before_render_function
8402
		 * @param int           $priority
8403
		 * @param bool          $show_submenu
8404
		 * @param string        $class Since 1.2.1.5 can add custom classes to menu items.
8405
		 */
8406
		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...
8407
			$menu_title,
8408
			$render_function,
8409
			$page_title = false,
8410
			$capability = 'manage_options',
8411
			$menu_slug = false,
8412
			$before_render_function = false,
8413
			$priority = WP_FS__DEFAULT_PRIORITY,
8414
			$show_submenu = true,
8415
			$class = ''
8416
		) {
8417
			$this->_logger->entrance( 'Title = ' . $menu_title );
8418
8419
			if ( $this->is_addon() ) {
8420
				$parent_fs = $this->get_parent_instance();
8421
8422
				if ( is_object( $parent_fs ) ) {
8423
					$parent_fs->add_submenu_item(
8424
						$menu_title,
8425
						$render_function,
8426
						$page_title,
8427
						$capability,
8428
						$menu_slug,
8429
						$before_render_function,
8430
						$priority,
8431
						$show_submenu,
8432
						$class
8433
					);
8434
8435
					return;
8436
				}
8437
			}
8438
8439
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
8440
				$this->_menu_items[ $priority ] = array();
8441
			}
8442
8443
			$this->_menu_items[ $priority ][] = array(
8444
				'page_title'             => is_string( $page_title ) ? $page_title : $menu_title,
8445
				'menu_title'             => $menu_title,
8446
				'capability'             => $capability,
8447
				'menu_slug'              => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ),
8448
				'render_function'        => $render_function,
8449
				'before_render_function' => $before_render_function,
8450
				'show_submenu'           => $show_submenu,
8451
				'class'                  => $class,
8452
			);
8453
		}
8454
8455
		/**
8456
		 * @author Vova Feldman (@svovaf)
8457
		 * @since  1.0.1
8458
		 *
8459
		 * @param string $menu_title
8460
		 * @param string $url
8461
		 * @param bool   $menu_slug
8462
		 * @param string $capability
8463
		 * @param int    $priority
8464
		 *
8465
		 */
8466
		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...
8467
			$menu_title,
8468
			$url,
8469
			$menu_slug = false,
8470
			$capability = 'read',
8471
			$priority = WP_FS__DEFAULT_PRIORITY
8472
		) {
8473
			$this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url );
8474
8475
			if ( $this->is_addon() ) {
8476
				$parent_fs = $this->get_parent_instance();
8477
8478
				if ( is_object( $parent_fs ) ) {
8479
					$parent_fs->add_submenu_link_item(
8480
						$menu_title,
8481
						$url,
8482
						$menu_slug,
8483
						$capability,
8484
						$priority
8485
					);
8486
8487
					return;
8488
				}
8489
			}
8490
8491
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
8492
				$this->_menu_items[ $priority ] = array();
8493
			}
8494
8495
			$this->_menu_items[ $priority ][] = array(
8496
				'menu_title'             => $menu_title,
8497
				'capability'             => $capability,
8498
				'menu_slug'              => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ),
8499
				'url'                    => $url,
8500
				'page_title'             => $menu_title,
8501
				'render_function'        => 'fs_dummy',
8502
				'before_render_function' => '',
8503
			);
8504
		}
8505
8506
		#endregion ------------------------------------------------------------------
8507
8508
		#--------------------------------------------------------------------------------
8509
		#region Actions / Hooks / Filters
8510
		#--------------------------------------------------------------------------------
8511
8512
		/**
8513
		 * @author Vova Feldman (@svovaf)
8514
		 * @since  1.1.7
8515
		 *
8516
		 * @param string $tag
8517
		 *
8518
		 * @return string
8519
		 */
8520
		public function get_action_tag( $tag ) {
8521
			return self::get_action_tag_static( $tag, $this->_slug );
8522
		}
8523
8524
		/**
8525
		 * @author Vova Feldman (@svovaf)
8526
		 * @since  1.2.1.6
8527
		 *
8528
		 * @param string $tag
8529
		 * @param string $slug
8530
		 *
8531
		 * @return string
8532
		 */
8533
		static function get_action_tag_static( $tag, $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...
8534
			return "fs_{$tag}" . ( empty( $slug ) ? '' : "_{$slug}" );
8535
		}
8536
8537
		/**
8538
		 * @author Vova Feldman (@svovaf)
8539
		 * @since  1.2.1
8540
		 *
8541
		 * @param string $tag
8542
		 *
8543
		 * @return string
8544
		 */
8545
		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...
8546
			return self::get_ajax_action_static( $tag, $this->_slug );
8547
		}
8548
8549
		/**
8550
		 * @author Vova Feldman (@svovaf)
8551
		 * @since  1.2.1.7
8552
		 *
8553
		 * @param string $tag
8554
		 *
8555
		 * @return string
8556
		 */
8557
		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...
8558
			return wp_create_nonce( $this->get_ajax_action( $tag ) );
8559
		}
8560
8561
		/**
8562
		 * @author Vova Feldman (@svovaf)
8563
		 * @since  1.2.1.7
8564
		 *
8565
		 * @param string $tag
8566
		 */
8567
		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...
8568
			check_ajax_referer( $this->get_ajax_action( $tag ), 'security' );
8569
		}
8570
8571
		/**
8572
		 * @author Vova Feldman (@svovaf)
8573
		 * @since  1.2.1.6
8574
		 *
8575
		 * @param string $tag
8576
		 * @param string $slug
8577
		 *
8578
		 * @return string
8579
		 */
8580
		private static function get_ajax_action_static( $tag, $slug = '' ) {
8581
			$action = "fs_{$tag}";
8582
8583
			if ( ! empty( $slug ) ) {
8584
				$action .= "_{$slug}";
8585
			}
8586
8587
			return $action;
8588
		}
8589
8590
		/**
8591
		 * Do action, specific for the current context plugin.
8592
		 *
8593
		 * @author Vova Feldman (@svovaf)
8594
		 * @since  1.0.1
8595
		 *
8596
		 * @param string $tag     The name of the action to be executed.
8597
		 * @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...
8598
		 *                        functions hooked to the action. Default empty.
8599
		 *
8600
		 * @uses   do_action()
8601
		 */
8602
		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...
8603
			$this->_logger->entrance( $tag );
8604
8605
			$args = func_get_args();
8606
8607
			call_user_func_array( 'do_action', array_merge(
8608
					array( $this->get_action_tag( $tag ) ),
8609
					array_slice( $args, 1 ) )
8610
			);
8611
		}
8612
8613
		/**
8614
		 * Add action, specific for the current context plugin.
8615
		 *
8616
		 * @author Vova Feldman (@svovaf)
8617
		 * @since  1.0.1
8618
		 *
8619
		 * @param string   $tag
8620
		 * @param callable $function_to_add
8621
		 * @param int      $priority
8622
		 * @param int      $accepted_args
8623
		 *
8624
		 * @uses   add_action()
8625
		 */
8626
		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...
8627
			$tag,
8628
			$function_to_add,
8629
			$priority = WP_FS__DEFAULT_PRIORITY,
8630
			$accepted_args = 1
8631
		) {
8632
			$this->_logger->entrance( $tag );
8633
8634
			add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
8635
		}
8636
8637
		/**
8638
		 * Add AJAX action, specific for the current context plugin.
8639
		 *
8640
		 * @author Vova Feldman (@svovaf)
8641
		 * @since  1.2.1
8642
		 *
8643
		 * @param string   $tag
8644
		 * @param callable $function_to_add
8645
		 * @param int      $priority
8646
		 *
8647
		 * @uses   add_action()
8648
		 *
8649
		 * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
8650
		 */
8651
		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...
8652
			$tag,
8653
			$function_to_add,
8654
			$priority = WP_FS__DEFAULT_PRIORITY
8655
		) {
8656
			$this->_logger->entrance( $tag );
8657
8658
			return self::add_ajax_action_static(
8659
				$tag,
8660
				$function_to_add,
8661
				$priority,
8662
				$this->_slug
8663
			);
8664
		}
8665
8666
		/**
8667
		 * Add AJAX action.
8668
		 *
8669
		 * @author Vova Feldman (@svovaf)
8670
		 * @since  1.2.1.6
8671
		 *
8672
		 * @param string   $tag
8673
		 * @param callable $function_to_add
8674
		 * @param int      $priority
8675
		 * @param string   $slug
8676
		 *
8677
		 * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
8678
		 * @uses   add_action()
8679
		 *
8680
		 */
8681
		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...
8682
			$tag,
8683
			$function_to_add,
8684
			$priority = WP_FS__DEFAULT_PRIORITY,
8685
			$slug = ''
8686
		) {
8687
			self::$_static_logger->entrance( $tag );
8688
8689
			if ( ! self::is_ajax_action_static( $tag, $slug ) ) {
8690
				return false;
8691
			}
8692
8693
			add_action(
8694
				'wp_ajax_' . self::get_ajax_action_static( $tag, $slug ),
8695
				$function_to_add,
8696
				$priority,
8697
				0
8698
			);
8699
8700
			self::$_static_logger->info( "$tag AJAX callback action added." );
8701
8702
			return true;
8703
		}
8704
8705
		/**
8706
		 * Send a JSON response back to an Ajax request.
8707
		 *
8708
		 * @author Vova Feldman (@svovaf)
8709
		 * @since  1.2.1.5
8710
		 *
8711
		 * @param mixed $response
8712
		 */
8713
		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...
8714
			wp_send_json( $response );
8715
		}
8716
8717
		/**
8718
		 * Send a JSON response back to an Ajax request, indicating success.
8719
		 *
8720
		 * @author Vova Feldman (@svovaf)
8721
		 * @since  1.2.1.5
8722
		 *
8723
		 * @param mixed $data Data to encode as JSON, then print and exit.
8724
		 */
8725
		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...
8726
			wp_send_json_success( $data );
8727
		}
8728
8729
		/**
8730
		 * Send a JSON response back to an Ajax request, indicating failure.
8731
		 *
8732
		 * @author Vova Feldman (@svovaf)
8733
		 * @since  1.2.1.5
8734
		 *
8735
		 * @param mixed $error Optional error message.
8736
		 */
8737
		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...
8738
			$result = array( 'success' => false );
8739
			if ( ! empty( $error ) ) {
8740
				$result['error'] = $error;
8741
			}
8742
8743
			wp_send_json( $result );
8744
		}
8745
8746
		/**
8747
		 * Apply filter, specific for the current context plugin.
8748
		 *
8749
		 * @author Vova Feldman (@svovaf)
8750
		 * @since  1.0.9
8751
		 *
8752
		 * @param string $tag   The name of the filter hook.
8753
		 * @param mixed  $value The value on which the filters hooked to `$tag` are applied on.
8754
		 *
8755
		 * @return mixed The filtered value after all hooked functions are applied to it.
8756
		 *
8757
		 * @uses   apply_filters()
8758
		 */
8759
		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...
8760
			$this->_logger->entrance( $tag );
8761
8762
			$args = func_get_args();
8763
			array_unshift( $args, $this->_slug );
8764
8765
			return call_user_func_array( 'fs_apply_filter', $args );
8766
		}
8767
8768
		/**
8769
		 * Add filter, specific for the current context plugin.
8770
		 *
8771
		 * @author Vova Feldman (@svovaf)
8772
		 * @since  1.0.9
8773
		 *
8774
		 * @param string   $tag
8775
		 * @param callable $function_to_add
8776
		 * @param int      $priority
8777
		 * @param int      $accepted_args
8778
		 *
8779
		 * @uses   add_filter()
8780
		 */
8781
		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...
8782
			$this->_logger->entrance( $tag );
8783
8784
			add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
8785
		}
8786
8787
		/**
8788
		 * Check if has filter.
8789
		 *
8790
		 * @author Vova Feldman (@svovaf)
8791
		 * @since  1.1.4
8792
		 *
8793
		 * @param string        $tag
8794
		 * @param callable|bool $function_to_check Optional. The callback to check for. Default false.
8795
		 *
8796
		 * @return false|int
8797
		 *
8798
		 * @uses   has_filter()
8799
		 */
8800
		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...
8801
			$this->_logger->entrance( $tag );
8802
8803
			return has_filter( $this->get_action_tag( $tag ), $function_to_check );
8804
		}
8805
8806
		#endregion
8807
8808
		/**
8809
		 * Override default i18n text phrases.
8810
		 *
8811
		 * @author Vova Feldman (@svovaf)
8812
		 * @since  1.1.6
8813
		 *
8814
		 * @param string[] string $key_value
8815
		 *
8816
		 * @uses   fs_override_i18n()
8817
		 */
8818
		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...
8819
			fs_override_i18n( $key_value, $this->_slug );
8820
		}
8821
8822
		/* Account Page
8823
		------------------------------------------------------------------------------------------------------------------*/
8824
		/**
8825
		 * Update site information.
8826
		 *
8827
		 * @author Vova Feldman (@svovaf)
8828
		 * @since  1.0.1
8829
		 *
8830
		 * @param bool $store Flush to Database if true.
8831
		 */
8832
		private function _store_site( $store = true ) {
8833
			$this->_logger->entrance();
8834
8835
			if ( empty( $this->_site->id ) ) {
8836
				$this->_logger->error( "Empty install ID, can't store site." );
8837
8838
				return;
8839
			}
8840
8841
			$encrypted_site       = clone $this->_site;
8842
			$encrypted_site->plan = self::_encrypt_entity( $this->_site->plan );
8843
8844
			$sites                 = self::get_all_sites();
8845
			$sites[ $this->_slug ] = $encrypted_site;
8846
			self::$_accounts->set_option( 'sites', $sites, $store );
8847
		}
8848
8849
		/**
8850
		 * Update plugin's plans information.
8851
		 *
8852
		 * @author Vova Feldman (@svovaf)
8853
		 * @since  1.0.2
8854
		 *
8855
		 * @param bool $store Flush to Database if true.
8856
		 */
8857
		private function _store_plans( $store = true ) {
8858
			$this->_logger->entrance();
8859
8860
			$plans = self::get_all_plans();
8861
8862
			// Copy plans.
8863
			$encrypted_plans = array();
8864
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
8865
				$encrypted_plans[] = self::_encrypt_entity( $this->_plans[ $i ] );
8866
			}
8867
8868
			$plans[ $this->_slug ] = $encrypted_plans;
8869
			self::$_accounts->set_option( 'plans', $plans, $store );
8870
		}
8871
8872
		/**
8873
		 * Update user's plugin licenses.
8874
		 *
8875
		 * @author Vova Feldman (@svovaf)
8876
		 * @since  1.0.5
8877
		 *
8878
		 * @param bool                $store
8879
		 * @param string|bool         $plugin_slug
8880
		 * @param FS_Plugin_License[] $licenses
8881
		 */
8882
		private function _store_licenses( $store = true, $plugin_slug = false, $licenses = array() ) {
8883
			$this->_logger->entrance();
8884
8885
			$all_licenses = self::get_all_licenses();
8886
8887
			if ( ! is_string( $plugin_slug ) ) {
8888
				$plugin_slug = $this->_slug;
8889
				$licenses    = $this->_licenses;
8890
			}
8891
8892
			if ( ! isset( $all_licenses[ $plugin_slug ] ) ) {
8893
				$all_licenses[ $plugin_slug ] = array();
8894
			}
8895
8896
			$all_licenses[ $plugin_slug ][ $this->_user->id ] = $licenses;
8897
8898
			self::$_accounts->set_option( 'licenses', $all_licenses, $store );
8899
		}
8900
8901
		/**
8902
		 * Update user information.
8903
		 *
8904
		 * @author Vova Feldman (@svovaf)
8905
		 * @since  1.0.1
8906
		 *
8907
		 * @param bool $store Flush to Database if true.
8908
		 */
8909
		private function _store_user( $store = true ) {
8910
			$this->_logger->entrance();
8911
8912
			if ( empty( $this->_user->id ) ) {
8913
				$this->_logger->error( "Empty user ID, can't store user." );
8914
8915
				return;
8916
			}
8917
8918
			$users                     = self::get_all_users();
8919
			$users[ $this->_user->id ] = $this->_user;
8920
			self::$_accounts->set_option( 'users', $users, $store );
8921
		}
8922
8923
		/**
8924
		 * Update new updates information.
8925
		 *
8926
		 * @author Vova Feldman (@svovaf)
8927
		 * @since  1.0.4
8928
		 *
8929
		 * @param FS_Plugin_Tag|null $update
8930
		 * @param bool               $store Flush to Database if true.
8931
		 * @param bool|number        $plugin_id
8932
		 */
8933
		private function _store_update( $update, $store = true, $plugin_id = false ) {
8934
			$this->_logger->entrance();
8935
8936
			if ( $update instanceof FS_Plugin_Tag ) {
8937
				$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...
8938
			}
8939
8940
			if ( ! is_numeric( $plugin_id ) ) {
8941
				$plugin_id = $this->_plugin->id;
8942
			}
8943
8944
			$updates               = self::get_all_updates();
8945
			$updates[ $plugin_id ] = $update;
8946
			self::$_accounts->set_option( 'updates', $updates, $store );
8947
		}
8948
8949
		/**
8950
		 * Update new updates information.
8951
		 *
8952
		 * @author   Vova Feldman (@svovaf)
8953
		 * @since    1.0.6
8954
		 *
8955
		 * @param FS_Plugin[] $plugin_addons
8956
		 * @param bool        $store Flush to Database if true.
8957
		 */
8958
		private function _store_addons( $plugin_addons, $store = true ) {
8959
			$this->_logger->entrance();
8960
8961
			$addons                       = self::get_all_addons();
8962
			$addons[ $this->_plugin->id ] = $plugin_addons;
8963
			self::$_accounts->set_option( 'addons', $addons, $store );
8964
		}
8965
8966
		/**
8967
		 * Delete plugin's associated add-ons.
8968
		 *
8969
		 * @author   Vova Feldman (@svovaf)
8970
		 * @since    1.0.8
8971
		 *
8972
		 * @param bool $store
8973
		 *
8974
		 * @return bool
8975
		 */
8976
		private function _delete_account_addons( $store = true ) {
8977
			$all_addons = self::get_all_account_addons();
8978
8979
			if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) {
8980
				return false;
8981
			}
8982
8983
			unset( $all_addons[ $this->_plugin->id ] );
8984
8985
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
8986
8987
			return true;
8988
		}
8989
8990
		/**
8991
		 * Update account add-ons list.
8992
		 *
8993
		 * @author   Vova Feldman (@svovaf)
8994
		 * @since    1.0.6
8995
		 *
8996
		 * @param FS_Plugin[] $addons
8997
		 * @param bool        $store Flush to Database if true.
8998
		 */
8999
		private function _store_account_addons( $addons, $store = true ) {
9000
			$this->_logger->entrance();
9001
9002
			$all_addons                       = self::get_all_account_addons();
9003
			$all_addons[ $this->_plugin->id ] = $addons;
9004
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
9005
		}
9006
9007
		/**
9008
		 * Store account params in the Database.
9009
		 *
9010
		 * @author Vova Feldman (@svovaf)
9011
		 * @since  1.0.1
9012
		 */
9013
		private function _store_account() {
9014
			$this->_logger->entrance();
9015
9016
			$this->_store_site( false );
9017
			$this->_store_user( false );
9018
			$this->_store_plans( false );
9019
			$this->_store_licenses( false );
9020
9021
			self::$_accounts->store();
9022
		}
9023
9024
		/**
9025
		 * Sync user's information.
9026
		 *
9027
		 * @author Vova Feldman (@svovaf)
9028
		 * @since  1.0.3
9029
		 * @uses   FS_Api
9030
		 */
9031
		private function _handle_account_user_sync() {
9032
			$this->_logger->entrance();
9033
9034
			$api = $this->get_api_user_scope();
9035
9036
			// Get user's information.
9037
			$user = $api->get( '/', true );
9038
9039
			if ( isset( $user->id ) ) {
9040
				$this->_user->first = $user->first;
9041
				$this->_user->last  = $user->last;
9042
				$this->_user->email = $user->email;
9043
9044
				$is_menu_item_account_visible = $this->is_submenu_item_visible( 'account' );
9045
9046
				if ( $user->is_verified &&
9047
				     ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified )
9048
				) {
9049
					$this->_user->is_verified = true;
9050
9051
					$this->do_action( 'account_email_verified', $user->email );
9052
9053
					$this->_admin_notices->add(
9054
						$this->get_text( 'email-verified-message' ),
9055
						$this->get_text( 'right-on' ) . '!',
9056
						'success',
9057
						// Make admin sticky if account menu item is invisible,
9058
						// since the page will be auto redirected to the plugin's
9059
						// main settings page, and the non-sticky message
9060
						// will disappear.
9061
						! $is_menu_item_account_visible,
9062
						false,
9063
						'email_verified'
9064
					);
9065
				}
9066
9067
				// Flush user details to DB.
9068
				$this->_store_user();
9069
9070
				$this->do_action( 'after_account_user_sync', $user );
9071
9072
				/**
9073
				 * If account menu item is hidden, redirect to plugin's main settings page.
9074
				 *
9075
				 * @author Vova Feldman (@svovaf)
9076
				 * @since  1.1.6
9077
				 *
9078
				 * @link   https://github.com/Freemius/wordpress-sdk/issues/6
9079
				 */
9080
				if ( ! $is_menu_item_account_visible ) {
9081
					fs_redirect( $this->_get_admin_page_url() );
9082
				}
9083
			}
9084
		}
9085
9086
		/**
9087
		 * @param bool $store
9088
		 *
9089
		 * @return FS_Plugin_Plan|object|false
9090
		 */
9091
		private function _enrich_site_plan( $store = true ) {
9092
			// Try to load plan from local cache.
9093
			$plan = $this->_get_plan_by_id( $this->_site->plan->id );
9094
9095
			if ( false === $plan ) {
9096
				$plan = $this->_fetch_site_plan();
9097
			}
9098
9099
			if ( $plan instanceof FS_Plugin_Plan ) {
9100
				$this->_update_plan( $plan, $store );
9101
			}
9102
9103
			return $plan;
9104
		}
9105
9106
		/**
9107
		 * @author Vova Feldman (@svovaf)
9108
		 * @since  1.0.9
9109
		 * @uses   FS_Api
9110
		 *
9111
		 * @param bool $store
9112
		 *
9113
		 * @return FS_Plugin_Plan|object|false
9114
		 */
9115
		private function _enrich_site_trial_plan( $store = true ) {
9116
			// Try to load plan from local cache.
9117
			$trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id );
9118
9119
			if ( false === $trial_plan ) {
9120
				$trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id );
9121
			}
9122
9123
			if ( $trial_plan instanceof FS_Plugin_Plan ) {
9124
				$this->_storage->store( 'trial_plan', $trial_plan, $store );
9125
			}
9126
9127
			return $trial_plan;
9128
		}
9129
9130
		/**
9131
		 * @author Vova Feldman (@svovaf)
9132
		 * @since  1.0.9
9133
		 * @uses   FS_Api
9134
		 *
9135
		 * @param number|bool $license_id
9136
		 *
9137
		 * @return FS_Subscription|object|bool
9138
		 */
9139
		private function _fetch_site_license_subscription( $license_id = false ) {
9140
			$this->_logger->entrance();
9141
			$api = $this->get_api_site_scope();
9142
9143
			if ( ! is_numeric( $license_id ) ) {
9144
				$license_id = $this->_license->id;
9145
			}
9146
9147
			$result = $api->get( "/licenses/{$license_id}/subscriptions.json", true );
9148
9149
			return ! isset( $result->error ) ?
9150
				( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ?
9151
					new FS_Subscription( $result->subscriptions[0] ) :
9152
					false
9153
				) :
9154
				$result;
9155
		}
9156
9157
		/**
9158
		 * @author Vova Feldman (@svovaf)
9159
		 * @since  1.0.4
9160
		 * @uses   FS_Api
9161
		 *
9162
		 * @param number|bool $plan_id
9163
		 *
9164
		 * @return FS_Plugin_Plan|object
9165
		 */
9166
		private function _fetch_site_plan( $plan_id = false ) {
9167
			$this->_logger->entrance();
9168
			$api = $this->get_api_site_scope();
9169
9170
			if ( ! is_numeric( $plan_id ) ) {
9171
				$plan_id = $this->_site->plan->id;
9172
			}
9173
9174
			$plan = $api->get( "/plans/{$plan_id}.json", true );
9175
9176
			return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan;
9177
		}
9178
9179
		/**
9180
		 * @author Vova Feldman (@svovaf)
9181
		 * @since  1.0.5
9182
		 * @uses   FS_Api
9183
		 *
9184
		 * @return FS_Plugin_Plan[]|object
9185
		 */
9186
		private function _fetch_plugin_plans() {
9187
			$this->_logger->entrance();
9188
			$api = $this->get_api_site_scope();
9189
9190
			$result = $api->get( '/plans.json', true );
9191
9192
			if ( $this->is_api_result_object( $result, 'plans' ) && is_array( $result->plans ) ) {
9193
				for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) {
9194
					$result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] );
9195
				}
9196
9197
				$result = $result->plans;
9198
			}
9199
9200
			return $result;
9201
		}
9202
9203
		/**
9204
		 * @author Vova Feldman (@svovaf)
9205
		 * @since  1.0.5
9206
		 * @uses   FS_Api
9207
		 *
9208
		 * @param number|bool $plugin_id
9209
		 * @param number|bool $site_license_id
9210
		 *
9211
		 * @return FS_Plugin_License[]|object
9212
		 */
9213
		private function _fetch_licenses( $plugin_id = false, $site_license_id = false ) {
9214
			$this->_logger->entrance();
9215
9216
			$api = $this->get_api_user_scope();
9217
9218
			if ( ! is_numeric( $plugin_id ) ) {
9219
				$plugin_id = $this->_plugin->id;
9220
			}
9221
9222
			$result = $api->get( "/plugins/{$plugin_id}/licenses.json", true );
9223
9224
			$is_site_license_synced = false;
9225
9226
			$api_errors = array();
9227
9228
			if ( $this->is_api_result_object( $result, 'licenses' ) &&
9229
			     is_array( $result->licenses )
9230
			) {
9231
				for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) {
9232
					$result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] );
9233
9234
					if ( ( ! $is_site_license_synced ) && is_numeric( $site_license_id ) ) {
9235
						$is_site_license_synced = ( $site_license_id == $result->licenses[ $i ]->id );
9236
					}
9237
				}
9238
9239
				$result = $result->licenses;
9240
			} else {
9241
				$api_errors[] = $result;
9242
				$result       = array();
9243
			}
9244
9245
			if ( ! $is_site_license_synced ) {
9246
				$api = $this->get_api_site_scope();
9247
9248
				if ( is_numeric( $site_license_id ) ) {
9249
					// Try to retrieve a foreign license that is linked to the install.
9250
					$api_result = $api->call( '/licenses.json' );
9251
9252
					if ( $this->is_api_result_object( $api_result, 'licenses' ) &&
9253
					     is_array( $api_result->licenses )
9254
					) {
9255
						$licenses = $api_result->licenses;
9256
9257
						if ( ! empty( $licenses ) ) {
9258
							$result[] = new FS_Plugin_License( $licenses[0] );
9259
						}
9260
					} else {
9261
						$api_errors[] = $api_result;
9262
					}
9263
				} else if ( is_object( $this->_license ) ) {
9264
					// Fetch foreign license by ID and license key.
9265
					$license = $api->get( "/licenses/{$this->_license->id}.json?license_key=" .
9266
					                      urlencode( $this->_license->secret_key ) );
9267
9268
					if ( $this->is_api_result_entity( $license ) ) {
9269
						$result[] = new FS_Plugin_License( $license );
9270
					} else {
9271
						$api_errors[] = $license;
9272
					}
9273
				}
9274
			}
9275
9276
			if ( is_array( $result ) && 0 < count( $result ) ) {
9277
				// If found at least one license, return license collection even if there are errors.
9278
				return $result;
9279
			}
9280
9281
			if ( ! empty( $api_errors ) ) {
9282
				// If found any errors and no licenses, return first error.
9283
				return $api_errors[0];
9284
			}
9285
9286
			// Fallback to empty licenses list.
9287
			return $result;
9288
		}
9289
9290
		/**
9291
		 * @author Vova Feldman (@svovaf)
9292
		 * @since  1.2.0
9293
		 * @uses   FS_Api
9294
		 *
9295
		 * @param number|bool $plugin_id
9296
		 *
9297
		 * @return FS_Payment[]|object
9298
		 */
9299
		function _fetch_payments( $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...
9300
			$this->_logger->entrance();
9301
9302
			$api = $this->get_api_user_scope();
9303
9304
			if ( ! is_numeric( $plugin_id ) ) {
9305
				$plugin_id = $this->_plugin->id;
9306
			}
9307
9308
			$result = $api->get( "/plugins/{$plugin_id}/payments.json?include_addons=true", true );
9309
9310
			if ( ! isset( $result->error ) ) {
9311
				for ( $i = 0, $len = count( $result->payments ); $i < $len; $i ++ ) {
9312
					$result->payments[ $i ] = new FS_Payment( $result->payments[ $i ] );
9313
				}
9314
				$result = $result->payments;
9315
			}
9316
9317
			return $result;
9318
		}
9319
9320
		/**
9321
		 * @author Vova Feldman (@svovaf)
9322
		 * @since  1.2.1.5
9323
		 * @uses   FS_Api
9324
		 *
9325
		 * @return \FS_Billing|mixed
9326
		 */
9327
		function _fetch_billing() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, 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
			require_once WP_FS__DIR_INCLUDES . '/entities/class-fs-billing.php';
9329
9330
			$billing = $this->get_api_user_scope()->call( 'billing.json' );
9331
9332
			if ( $this->is_api_result_entity( $billing ) ) {
9333
				$billing = new FS_Billing( $billing );
9334
			}
9335
9336
			return $billing;
9337
		}
9338
9339
		/**
9340
		 * @author Vova Feldman (@svovaf)
9341
		 * @since  1.0.4
9342
		 *
9343
		 * @param FS_Plugin_Plan $plan
9344
		 * @param bool           $store
9345
		 */
9346
		private function _update_plan( $plan, $store = false ) {
9347
			$this->_logger->entrance();
9348
9349
			$this->_site->plan = $plan;
9350
			$this->_store_site( $store );
9351
		}
9352
9353
		/**
9354
		 * @author Vova Feldman (@svovaf)
9355
		 * @since  1.0.5
9356
		 *
9357
		 * @param FS_Plugin_License[] $licenses
9358
		 * @param string|bool         $plugin_slug
9359
		 */
9360
		private function _update_licenses( $licenses, $plugin_slug = false ) {
9361
			$this->_logger->entrance();
9362
9363
			if ( is_array( $licenses ) ) {
9364
				for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) {
9365
					$licenses[ $i ]->updated = time();
9366
				}
9367
			}
9368
9369
			if ( ! is_string( $plugin_slug ) ) {
9370
				$this->_licenses = $licenses;
9371
			}
9372
9373
			$this->_store_licenses( true, $plugin_slug, $licenses );
9374
		}
9375
9376
		/**
9377
		 * @author Vova Feldman (@svovaf)
9378
		 * @since  1.0.4
9379
		 *
9380
		 * @param bool|number $plugin_id
9381
		 * @param bool        $flush Since 1.1.7.3
9382
		 *
9383
		 * @return object|false New plugin tag info if exist.
9384
		 */
9385
		private function _fetch_newer_version( $plugin_id = false, $flush = true ) {
9386
			$latest_tag = $this->_fetch_latest_version( $plugin_id, $flush );
9387
9388
			if ( ! is_object( $latest_tag ) ) {
9389
				return false;
9390
			}
9391
9392
			// Check if version is actually newer.
9393
			$has_new_version =
9394
				// If it's an non-installed add-on then always return latest.
9395
				( $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 9385 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...
9396
				// Compare versions.
9397
				version_compare( $this->get_plugin_version(), $latest_tag->version, '<' );
9398
9399
			$this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' );
9400
9401
			return $has_new_version ? $latest_tag : false;
9402
		}
9403
9404
		/**
9405
		 * @author Vova Feldman (@svovaf)
9406
		 * @since  1.0.5
9407
		 *
9408
		 * @param bool|number $plugin_id
9409
		 * @param bool        $flush Since 1.1.7.3
9410
		 *
9411
		 * @return bool|FS_Plugin_Tag
9412
		 */
9413
		function get_update( $plugin_id = false, $flush = 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...
9414
			$this->_logger->entrance();
9415
9416
			if ( ! is_numeric( $plugin_id ) ) {
9417
				$plugin_id = $this->_plugin->id;
9418
			}
9419
9420
			$this->check_updates( true, $plugin_id, $flush );
9421
			$updates = $this->get_all_updates();
9422
9423
			return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
9424
		}
9425
9426
		/**
9427
		 * Check if site assigned with active license.
9428
		 *
9429
		 * @author     Vova Feldman (@svovaf)
9430
		 * @since      1.0.6
9431
		 *
9432
		 * @deprecated Please use has_active_valid_license() instead because license can be cancelled.
9433
		 */
9434
		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...
9435
			return (
9436
				is_object( $this->_license ) &&
9437
				is_numeric( $this->_license->id ) &&
9438
				! $this->_license->is_expired()
9439
			);
9440
		}
9441
9442
		/**
9443
		 * Check if site assigned with active & valid (not expired) license.
9444
		 *
9445
		 * @author Vova Feldman (@svovaf)
9446
		 * @since  1.2.1
9447
		 */
9448
		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...
9449
			return (
9450
				is_object( $this->_license ) &&
9451
				is_numeric( $this->_license->id ) &&
9452
				$this->_license->is_active() &&
9453
				$this->_license->is_valid()
9454
			);
9455
		}
9456
9457
		/**
9458
		 * Check if site assigned with license with enabled features.
9459
		 *
9460
		 * @author Vova Feldman (@svovaf)
9461
		 * @since  1.0.6
9462
		 *
9463
		 * @return bool
9464
		 */
9465
		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...
9466
			return (
9467
				is_object( $this->_license ) &&
9468
				is_numeric( $this->_license->id ) &&
9469
				$this->_license->is_features_enabled()
9470
			);
9471
		}
9472
9473
		/**
9474
		 * Check if user is a trial or have feature enabled license.
9475
		 *
9476
		 * @author Vova Feldman (@svovaf)
9477
		 * @since  1.1.7
9478
		 *
9479
		 * @return bool
9480
		 */
9481
		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...
9482
			return $this->is_trial() || $this->has_features_enabled_license();
9483
		}
9484
9485
		/**
9486
		 * Checks if the current user can activate plugins or switch themes. Note that this method should only be used
9487
		 * after the `init` action is triggered because it is using `current_user_can()` which is only functional after
9488
		 * the context user is authenticated.
9489
		 *
9490
		 * @author Leo Fajardo (@leorw)
9491
		 * @since  1.2.2
9492
		 *
9493
		 * @return bool
9494
		 */
9495
		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...
9496
			return current_user_can( 'activate_plugins' );
9497
		}
9498
9499
		/**
9500
		 * Sync site's plan.
9501
		 *
9502
		 * @author Vova Feldman (@svovaf)
9503
		 * @since  1.0.3
9504
		 *
9505
		 * @uses   FS_Api
9506
		 *
9507
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
9508
		 *                         the admin.
9509
		 */
9510
		private function _sync_license( $background = false ) {
9511
			$this->_logger->entrance();
9512
9513
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
9514
9515
			$is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() );
9516
9517
			if ( $is_addon_sync ) {
9518
				$this->_sync_addon_license( $plugin_id, $background );
9519
			} else {
9520
				$this->_sync_plugin_license( $background );
9521
			}
9522
9523
			$this->do_action( 'after_account_plan_sync', $this->_site->plan->name );
9524
		}
9525
9526
		/**
9527
		 * Sync plugin's add-on license.
9528
		 *
9529
		 * @author Vova Feldman (@svovaf)
9530
		 * @since  1.0.6
9531
		 * @uses   FS_Api
9532
		 *
9533
		 * @param number $addon_id
9534
		 * @param bool   $background
9535
		 */
9536
		private function _sync_addon_license( $addon_id, $background ) {
9537
			$this->_logger->entrance();
9538
9539
			if ( $this->is_addon_activated( $addon_id ) ) {
9540
				// If already installed, use add-on sync.
9541
				$fs_addon = self::get_instance_by_id( $addon_id );
9542
				$fs_addon->_sync_license( $background );
9543
9544
				return;
9545
			}
9546
9547
			// Validate add-on exists.
9548
			$addon = $this->get_addon( $addon_id );
9549
9550
			if ( ! is_object( $addon ) ) {
9551
				return;
9552
			}
9553
9554
			// Add add-on into account add-ons.
9555
			$account_addons = $this->get_account_addons();
9556
			if ( ! is_array( $account_addons ) ) {
9557
				$account_addons = array();
9558
			}
9559
			$account_addons[] = $addon->id;
9560
			$account_addons   = array_unique( $account_addons );
9561
			$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...
9562
9563
			// Load add-on licenses.
9564
			$licenses = $this->_fetch_licenses( $addon->id );
9565
9566
			// Sync add-on licenses.
9567
			if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) {
9568
				$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 9564 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...
9569
9570
				if ( ! $this->is_addon_installed( $addon->slug ) && 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 9564 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...
9571
					$plans_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$addon_id}/plans.json" );
9572
9573
					if ( ! isset( $plans_result->error ) ) {
9574
						$plans = array();
9575
						foreach ( $plans_result->plans as $plan ) {
9576
							$plans[] = new FS_Plugin_Plan( $plan );
9577
						}
9578
9579
						$this->_admin_notices->add_sticky(
9580
							FS_Plan_Manager::instance()->has_free_plan( $plans ) ?
9581
								sprintf(
9582
									$this->get_text( 'addon-successfully-upgraded-message' ),
9583
									$addon->title
9584
								) . ' ' . $this->get_latest_download_link(
9585
									$this->get_text( 'download-latest-version' ),
9586
									$addon_id
9587
								)
9588
								:
9589
								sprintf(
9590
									$this->get_text( 'addon-successfully-purchased-message' ),
9591
									$addon->title
9592
								) . ' ' . $this->get_latest_download_link(
9593
									$this->get_text( 'download-latest-version' ),
9594
									$addon_id
9595
								),
9596
							'addon_plan_upgraded_' . $addon->slug,
9597
							$this->get_text( 'yee-haw' ) . '!'
9598
						);
9599
					}
9600
				}
9601
			}
9602
		}
9603
9604
		/**
9605
		 * Sync site's plugin plan.
9606
		 *
9607
		 * @author Vova Feldman (@svovaf)
9608
		 * @since  1.0.6
9609
		 * @uses   FS_Api
9610
		 *
9611
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
9612
		 *                         the admin.
9613
		 */
9614
		private function _sync_plugin_license( $background = false ) {
9615
			$this->_logger->entrance();
9616
9617
			/**
9618
			 * Sync site info.
9619
			 *
9620
			 * @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.
9621
			 */
9622
			$site = $this->send_install_update( array(), true );
9623
9624
			$plan_change = 'none';
9625
9626
			if ( ! $this->is_api_result_entity( $site ) ) {
9627
				// Show API messages only if not background sync or if paying customer.
9628
				if ( ! $background || $this->is_paying() ) {
9629
					// Try to ping API to see if not blocked.
9630
					if ( ! FS_Api::test() ) {
9631
						/**
9632
						 * Failed to ping API - blocked!
9633
						 *
9634
						 * @author Vova Feldman (@svovaf)
9635
						 * @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.
9636
						 */
9637
						$api = $this->get_api_site_scope();
9638
9639
						if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) {
9640
							self::$_global_admin_notices->add(
9641
								sprintf(
9642
									$this->get_text( 'server-blocking-access' ),
9643
									$this->get_plugin_name(),
9644
									'<a href="' . $api->get_url() . '" target="_blank">' . $api->get_url() . '</a>'
9645
								) . '<br> ' . $this->get_text( 'server-error-message' ) . var_export( $site->error, true ),
9646
								$this->get_text( 'oops' ) . '...',
9647
								'error',
9648
								$background,
9649
								false,
9650
								'api_blocked'
9651
							);
9652
						}
9653
					} else {
9654
						// Authentication params are broken.
9655
						$this->_admin_notices->add(
9656
							$this->get_text( 'wrong-authentication-param-message' ),
9657
							$this->get_text( 'oops' ) . '...',
9658
							'error'
9659
						);
9660
					}
9661
				}
9662
9663
				// No reason to continue with license sync while there are API issues.
9664
				return;
9665
			}
9666
9667
			// Remove sticky API connectivity message.
9668
			self::$_global_admin_notices->remove_sticky( 'api_blocked' );
9669
9670
			$site = new FS_Site( $site );
0 ignored issues
show
Bug introduced by
It seems like $site defined by new \FS_Site($site) on line 9670 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...
9671
9672
			// Sync plans.
9673
			$this->_sync_plans();
9674
9675
			if ( ! $this->has_paid_plan() ) {
9676
				$this->_site = $site;
9677
				$this->_enrich_site_plan( true );
9678
				$this->_store_site();
9679
			} else {
9680
				/**
9681
				 * Sync licenses. Pass the site's license ID so that the foreign licenses will be fetched if the license
9682
				 * associated with that ID is not included in the user's licenses collection.
9683
				 */
9684
				$this->_sync_licenses( $site->license_id );
9685
9686
				// Check if plan / license changed.
9687
				if ( ! FS_Entity::equals( $site->plan, $this->_site->plan ) ||
9688
				     // Check if trial started.
9689
				     $site->trial_plan_id != $this->_site->trial_plan_id ||
9690
				     $site->trial_ends != $this->_site->trial_ends ||
9691
				     // Check if license changed.
9692
				     $site->license_id != $this->_site->license_id
9693
				) {
9694
					if ( $site->is_trial() && ( ! $this->_site->is_trial() || $site->trial_ends != $this->_site->trial_ends ) ) {
9695
						// New trial started.
9696
						$this->_site = $site;
9697
						$plan_change = 'trial_started';
9698
9699
						// Store trial plan information.
9700
						$this->_enrich_site_trial_plan( true );
9701
9702
						// For trial with subscription use-case.
9703
						$new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id );
9704
9705
						if ( is_object( $new_license ) && $new_license->is_valid() ) {
9706
							$this->_site = $site;
9707
							$this->_update_site_license( $new_license );
9708
							$this->_store_licenses();
9709
							$this->_enrich_site_plan( true );
9710
9711
							$this->_sync_site_subscription( $this->_license );
9712
						}
9713
					} else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) {
9714
						// Was in trial, but now trial expired and no license ID.
9715
						// New trial started.
9716
						$this->_site = $site;
9717
						$plan_change = 'trial_expired';
9718
9719
						// Clear trial plan information.
9720
						$this->_storage->trial_plan = null;
9721
9722
					} else {
9723
						$is_free = $this->is_free_plan();
9724
9725
						// Make sure license exist and not expired.
9726
						$new_license = is_null( $site->license_id ) ?
9727
							null :
9728
							$this->_get_license_by_id( $site->license_id );
9729
9730
						if ( $is_free && is_null( $new_license ) && $this->has_any_license() && $this->_license->is_cancelled ) {
9731
							// License cancelled.
9732
							$this->_site = $site;
9733
							$this->_update_site_license( $new_license );
9734
							$this->_store_licenses();
9735
							$this->_enrich_site_plan( true );
9736
9737
							$plan_change = 'cancelled';
9738
						} 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...
9739
							// The license is expired, so ignore upgrade method.
9740
						} else {
9741
							// License changed.
9742
							$this->_site = $site;
9743
							$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 9726 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...
9744
							$this->_store_licenses();
9745
							$this->_enrich_site_plan( true );
9746
9747
							$plan_change = $is_free ?
9748
								'upgraded' :
9749
								( is_object( $new_license ) ?
9750
									'changed' :
9751
									'downgraded' );
9752
						}
9753
					}
9754
9755
					// Store updated site info.
9756
					$this->_store_site();
9757
				} else {
9758
					if ( is_object( $this->_license ) && $this->_license->is_expired() ) {
9759
						if ( ! $this->has_features_enabled_license() ) {
9760
							$this->_deactivate_license();
9761
							$plan_change = 'downgraded';
9762
						} else {
9763
							$plan_change = 'expired';
9764
						}
9765
					}
9766
9767
					if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) {
9768
						$this->_sync_site_subscription( $this->_license );
9769
					}
9770
				}
9771
			}
9772
9773
			if ( $this->has_paid_plan() ) {
9774
				switch ( $plan_change ) {
9775
					case 'none':
9776
						if ( ! $background && is_admin() ) {
9777
							$plan = $this->is_trial() ?
9778
								$this->_storage->trial_plan :
9779
								$this->_site->plan;
9780
9781
							if ( $plan->is_free() ) {
9782
								$this->_admin_notices->add(
9783
									sprintf(
9784
										$this->get_text( 'plan-did-not-change-message' ),
9785
										'<i><b>' . $plan->title . ( $this->is_trial() ? ' ' . $this->get_text( 'trial' ) : '' ) . '</b></i>'
9786
									) . ' ' . sprintf(
9787
										'<a href="%s">%s</a>',
9788
										$this->contact_url(
9789
											'bug',
9790
											sprintf( $this->get_text( 'plan-did-not-change-email-message' ),
9791
												strtoupper( $plan->name )
9792
											)
9793
										),
9794
										$this->get_text( 'contact-us-here' )
9795
									),
9796
									$this->get_text( 'hmm' ) . '...'
9797
								);
9798
							}
9799
						}
9800
						break;
9801
					case 'upgraded':
9802
						$this->_admin_notices->add_sticky(
9803
							sprintf(
9804
								$this->get_text( 'plan-upgraded-message' ),
9805
								'<i>' . $this->get_plugin_name() . '</i>'
9806
							) . $this->get_complete_upgrade_instructions(),
9807
							'plan_upgraded',
9808
							$this->get_text( 'yee-haw' ) . '!'
9809
						);
9810
9811
						$this->_admin_notices->remove_sticky( array(
9812
							'trial_started',
9813
							'trial_promotion',
9814
							'trial_expired',
9815
							'activation_complete',
9816
						) );
9817
						break;
9818
					case 'changed':
9819
						$this->_admin_notices->add_sticky(
9820
							sprintf(
9821
								$this->get_text( 'plan-changed-to-x-message' ),
9822
								$this->_site->plan->title
9823
							),
9824
							'plan_changed'
9825
						);
9826
9827
						$this->_admin_notices->remove_sticky( array(
9828
							'trial_started',
9829
							'trial_promotion',
9830
							'trial_expired',
9831
							'activation_complete',
9832
						) );
9833
						break;
9834
					case 'downgraded':
9835
						$this->_admin_notices->add_sticky(
9836
							sprintf( $this->get_text( 'license-expired-blocking-message' ) ),
9837
							'license_expired',
9838
							$this->get_text( 'hmm' ) . '...'
9839
						);
9840
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
9841
						break;
9842
					case 'cancelled':
9843
						$this->_admin_notices->add(
9844
							$this->get_text( 'license-cancelled' ) . ' ' .
9845
							sprintf(
9846
								'<a href="%s">%s</a>',
9847
								$this->contact_url( 'bug' ),
9848
								$this->get_text( 'contact-us-here' )
9849
							),
9850
							$this->get_text( 'hmm' ) . '...',
9851
							'error'
9852
						);
9853
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
9854
						break;
9855
					case 'expired':
9856
						$this->_admin_notices->add_sticky(
9857
							sprintf( $this->get_text( 'license-expired-non-blocking-message' ), $this->_site->plan->title ),
9858
							'license_expired',
9859
							$this->get_text( 'hmm' ) . '...'
9860
						);
9861
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
9862
						break;
9863
					case 'trial_started':
9864
						$this->_admin_notices->add_sticky(
9865
							sprintf(
9866
								$this->get_text( 'trial-started-message' ),
9867
								'<i>' . $this->get_plugin_name() . '</i>'
9868
							) . $this->get_complete_upgrade_instructions( $this->_storage->trial_plan->title ),
9869
							'trial_started',
9870
							$this->get_text( 'yee-haw' ) . '!'
9871
						);
9872
9873
						$this->_admin_notices->remove_sticky( array(
9874
							'trial_promotion',
9875
						) );
9876
						break;
9877
					case 'trial_expired':
9878
						$this->_admin_notices->add_sticky(
9879
							$this->get_text( 'trial-expired-message' ),
9880
							'trial_expired',
9881
							$this->get_text( 'hmm' ) . '...'
9882
						);
9883
						$this->_admin_notices->remove_sticky( array(
9884
							'trial_started',
9885
							'trial_promotion',
9886
							'plan_upgraded',
9887
						) );
9888
						break;
9889
				}
9890
			}
9891
9892
			if ( 'none' !== $plan_change ) {
9893
				$this->do_action( 'after_license_change', $plan_change, $this->_site->plan );
9894
			}
9895
		}
9896
9897
		/**
9898
		 * @author Vova Feldman (@svovaf)
9899
		 * @since  1.0.5
9900
		 *
9901
		 * @param bool $background
9902
		 */
9903
		protected function _activate_license( $background = false ) {
9904
			$this->_logger->entrance();
9905
9906
			$license_id = fs_request_get( 'license_id' );
9907
9908
			if ( FS_Plugin_License::is_valid_id( $license_id ) && $license_id == $this->_site->license_id ) {
9909
				// License is already activated.
9910
				return;
9911
			}
9912
9913
			$premium_license = FS_Plugin_License::is_valid_id( $license_id ) ?
9914
				$this->_get_license_by_id( $license_id ) :
9915
				$this->_get_available_premium_license();
9916
9917
			if ( ! is_object( $premium_license ) ) {
9918
				return;
9919
			}
9920
9921
			/**
9922
			 * If the premium license is already associated with the install, just
9923
			 * update the license reference (activation is not required).
9924
			 *
9925
			 * @since 1.1.9
9926
			 */
9927
			if ( $premium_license->id == $this->_site->license_id ) {
9928
				// License is already activated.
9929
				$this->_update_site_license( $premium_license );
9930
				$this->_enrich_site_plan( false );
9931
				$this->_store_account();
9932
9933
				return;
9934
			}
9935
9936
			if ( $this->_site->user_id != $premium_license->user_id ) {
9937
				$api_request_params = array( 'license_key' => $premium_license->secret_key );
9938
			} else {
9939
				$api_request_params = array();
9940
			}
9941
9942
			$api     = $this->get_api_site_scope();
9943
			$license = $api->call( "/licenses/{$premium_license->id}.json", 'put', $api_request_params );
9944
9945
			if ( ! $this->is_api_result_entity( $license ) ) {
9946
				if ( ! $background ) {
9947
					$this->_admin_notices->add( sprintf(
9948
						'%s %s',
9949
						$this->get_text( 'license-activation-failed-message' ),
9950
						( is_object( $license ) && isset( $license->error ) ?
9951
							$license->error->message :
9952
							sprintf( '%s<br><code>%s</code>',
9953
								$this->get_text( 'server-error-message' ),
9954
								var_export( $license, true )
9955
							)
9956
						)
9957
					),
9958
						$this->get_text( 'hmm' ) . '...',
9959
						'error'
9960
					);
9961
				}
9962
9963
				return;
9964
			}
9965
9966
			$premium_license = new FS_Plugin_License( $license );
9967
9968
			// Updated site plan.
9969
			$site = $this->get_api_site_scope()->get( '/', true );
9970
			if ( $this->is_api_result_entity( $site ) ) {
9971
				$this->_site = new FS_Site( $site );
9972
			}
9973
			$this->_update_site_license( $premium_license );
9974
			$this->_enrich_site_plan( false );
9975
9976
			$this->_store_account();
9977
9978
			if ( ! $background ) {
9979
				$this->_admin_notices->add_sticky(
9980
					$this->get_text( 'license-activated-message' ) .
9981
					$this->get_complete_upgrade_instructions(),
9982
					'license_activated',
9983
					$this->get_text( 'yee-haw' ) . '!'
9984
				);
9985
			}
9986
9987
			$this->_admin_notices->remove_sticky( array(
9988
				'trial_promotion',
9989
				'license_expired',
9990
			) );
9991
		}
9992
9993
		/**
9994
		 * @author Vova Feldman (@svovaf)
9995
		 * @since  1.0.5
9996
		 *
9997
		 * @param bool $show_notice
9998
		 */
9999
		protected function _deactivate_license( $show_notice = true ) {
10000
			$this->_logger->entrance();
10001
10002
			if ( ! is_object( $this->_license ) ) {
10003
				$this->_admin_notices->add(
10004
					sprintf( $this->get_text( 'no-active-license-message' ), $this->_site->plan->title ),
10005
					$this->get_text( 'hmm' ) . '...'
10006
				);
10007
10008
				return;
10009
			}
10010
10011
			$api     = $this->get_api_site_scope();
10012
			$license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' );
10013
10014
			if ( isset( $license->error ) ) {
10015
				$this->_admin_notices->add(
10016
					$this->get_text( 'license-deactivation-failed-message' ) . '<br> ' .
10017
					$this->get_text( 'server-error-message' ) . ' ' . var_export( $license->error, true ),
10018
					$this->get_text( 'hmm' ) . '...',
10019
					'error'
10020
				);
10021
10022
				return;
10023
			}
10024
10025
			// Update license cache.
10026
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
10027
				if ( $license->id == $this->_licenses[ $i ]->id ) {
10028
					$this->_licenses[ $i ] = new FS_Plugin_License( $license );
10029
				}
10030
			}
10031
10032
			// Updated site plan to default.
10033
			$this->_sync_plans();
10034
			$this->_site->plan->id = $this->_plans[0]->id;
10035
			// Unlink license from site.
10036
			$this->_update_site_license( null );
10037
			$this->_enrich_site_plan( false );
10038
10039
			$this->_store_account();
10040
10041
			if ( $show_notice ) {
10042
				$this->_admin_notices->add(
10043
					sprintf( $this->get_text( 'license-deactivation-message' ), $this->_site->plan->title ),
10044
					$this->get_text( 'ok' )
10045
				);
10046
			}
10047
10048
			$this->_admin_notices->remove_sticky( array(
10049
				'plan_upgraded',
10050
				'license_activated',
10051
			) );
10052
		}
10053
10054
		/**
10055
		 * Site plan downgrade.
10056
		 *
10057
		 * @author Vova Feldman (@svovaf)
10058
		 * @since  1.0.4
10059
		 *
10060
		 * @uses   FS_Api
10061
		 */
10062
		private function _downgrade_site() {
10063
			$this->_logger->entrance();
10064
10065
			$api  = $this->get_api_site_scope();
10066
			$site = $api->call( 'downgrade.json', 'put' );
10067
10068
			$plan_downgraded = false;
10069
			$plan            = false;
10070
			if ( $this->is_api_result_entity( $site ) ) {
10071
				$prev_plan_id = $this->_site->plan->id;
10072
10073
				// Update new site plan id.
10074
				$this->_site->plan->id = $site->plan_id;
10075
10076
				$plan         = $this->_enrich_site_plan();
10077
				$subscription = $this->_sync_site_subscription( $this->_license );
10078
10079
				// Plan downgraded if plan was changed or subscription was cancelled.
10080
				$plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) ||
10081
				                   ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() );
10082
			} 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...
10083
				// handle different error cases.
10084
10085
			}
10086
10087
			if ( $plan_downgraded ) {
10088
				// Remove previous sticky message about upgrade (if exist).
10089
				$this->_admin_notices->remove_sticky( 'plan_upgraded' );
10090
10091
				$this->_admin_notices->add(
10092
					sprintf( $this->get_text( 'plan-x-downgraded-message' ),
10093
						$plan->title,
10094
						human_time_diff( time(), strtotime( $this->_license->expiration ) )
10095
					)
10096
				);
10097
10098
				// Store site updates.
10099
				$this->_store_site();
10100
			} else {
10101
				$this->_admin_notices->add(
10102
					$this->get_text( 'plan-downgraded-failure-message' ),
10103
					$this->get_text( 'oops' ) . '...',
10104
					'error'
10105
				);
10106
			}
10107
		}
10108
10109
		/**
10110
		 * @author Vova Feldman (@svovaf)
10111
		 * @since  1.1.8.1
10112
		 *
10113
		 * @param bool|string $plan_name
10114
		 *
10115
		 * @return bool If trial was successfully started.
10116
		 */
10117
		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...
10118
			$this->_logger->entrance();
10119
10120
			if ( $this->is_trial() ) {
10121
				// Already in trial mode.
10122
				$this->_admin_notices->add(
10123
					$this->get_text( 'in-trial-mode' ),
10124
					$this->get_text( 'oops' ) . '...',
10125
					'error'
10126
				);
10127
10128
				return false;
10129
			}
10130
10131
			if ( $this->_site->is_trial_utilized() ) {
10132
				// Trial was already utilized.
10133
				$this->_admin_notices->add(
10134
					$this->get_text( 'trial-utilized' ),
10135
					$this->get_text( 'oops' ) . '...',
10136
					'error'
10137
				);
10138
10139
				return false;
10140
			}
10141
10142
			if ( false !== $plan_name ) {
10143
				$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 10117 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...
10144
10145
				if ( false === $plan ) {
10146
					// Plan doesn't exist.
10147
					$this->_admin_notices->add(
10148
						sprintf( $this->get_text( 'trial-plan-x-not-exist' ), $plan_name ),
10149
						$this->get_text( 'oops' ) . '...',
10150
						'error'
10151
					);
10152
10153
					return false;
10154
				}
10155
10156
				if ( ! $plan->has_trial() ) {
10157
					// Plan doesn't exist.
10158
					$this->_admin_notices->add(
10159
						sprintf( $this->get_text( 'plan-x-no-trial' ), $plan_name ),
10160
						$this->get_text( 'oops' ) . '...',
10161
						'error'
10162
					);
10163
10164
					return false;
10165
				}
10166
			} else {
10167
				if ( ! $this->has_trial_plan() ) {
10168
					// None of the plans have a trial.
10169
					$this->_admin_notices->add(
10170
						$this->get_text( 'no-trials' ),
10171
						$this->get_text( 'oops' ) . '...',
10172
						'error'
10173
					);
10174
10175
					return false;
10176
				}
10177
10178
				$plans_with_trial = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
10179
10180
				$plan = $plans_with_trial[0];
10181
			}
10182
10183
			$api  = $this->get_api_site_scope();
10184
			$plan = $api->call( "plans/{$plan->id}/trials.json", 'post' );
10185
10186
			if ( ! $this->is_api_result_entity( $plan ) ) {
10187
				// Some API error while trying to start the trial.
10188
				$this->_admin_notices->add(
10189
					$this->get_text( 'unexpected-api-error' ) . ' ' . var_export( $plan, true ),
10190
					$this->get_text( 'oops' ) . '...',
10191
					'error'
10192
				);
10193
10194
				return false;
10195
			}
10196
10197
			// Sync license.
10198
			$this->_sync_license();
10199
10200
			return $this->is_trial();
10201
		}
10202
10203
		/**
10204
		 * Cancel site trial.
10205
		 *
10206
		 * @author Vova Feldman (@svovaf)
10207
		 * @since  1.0.9
10208
		 *
10209
		 * @uses   FS_Api
10210
		 */
10211
		private function _cancel_trial() {
10212
			$this->_logger->entrance();
10213
10214
			if ( ! $this->is_trial() ) {
10215
				$this->_admin_notices->add(
10216
					$this->get_text( 'trial-cancel-no-trial-message' ),
10217
					$this->get_text( 'oops' ) . '...',
10218
					'error'
10219
				);
10220
10221
				return;
10222
			}
10223
10224
			$api  = $this->get_api_site_scope();
10225
			$site = $api->call( 'trials.json', 'delete' );
10226
10227
			$trial_cancelled = false;
10228
10229
			if ( $this->is_api_result_entity( $site ) ) {
10230
				$prev_trial_ends = $this->_site->trial_ends;
10231
10232
				if ( $this->is_paid_trial() ) {
10233
					$this->_license->expiration   = $site->trial_ends;
10234
					$this->_license->is_cancelled = true;
10235
					$this->_update_site_license( $this->_license );
10236
					$this->_store_licenses();
10237
10238
					// Clear subscription reference.
10239
					$this->_sync_site_subscription( null );
10240
				}
10241
10242
				// Update site info.
10243
				$this->_site = new FS_Site( $site );
10244
				$this->_enrich_site_plan();
10245
10246
				$trial_cancelled = ( $prev_trial_ends != $site->trial_ends );
10247
			} 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...
10248
				// handle different error cases.
10249
10250
			}
10251
10252
			if ( $trial_cancelled ) {
10253
				// Remove previous sticky messages about upgrade or trial (if exist).
10254
				$this->_admin_notices->remove_sticky( array(
10255
					'trial_started',
10256
					'trial_promotion',
10257
					'plan_upgraded',
10258
				) );
10259
10260
				// Store site updates.
10261
				$this->_store_site();
10262
10263
				if ( ! $this->is_addon() ||
10264
				     ! $this->deactivate_premium_only_addon_without_license( true )
10265
				) {
10266
					$this->_admin_notices->add(
10267
						sprintf( $this->get_text( 'trial-cancel-message' ), $this->_storage->trial_plan->title )
10268
					);
10269
				}
10270
10271
				// Clear trial plan information.
10272
				unset( $this->_storage->trial_plan );
10273
			} else {
10274
				$this->_admin_notices->add(
10275
					$this->get_text( 'trial-cancel-failure-message' ),
10276
					$this->get_text( 'oops' ) . '...',
10277
					'error'
10278
				);
10279
			}
10280
		}
10281
10282
		/**
10283
		 * @author Vova Feldman (@svovaf)
10284
		 * @since  1.0.6
10285
		 *
10286
		 * @param bool|number $plugin_id
10287
		 *
10288
		 * @return bool
10289
		 */
10290
		private function _is_addon_id( $plugin_id ) {
10291
			return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id );
10292
		}
10293
10294
		/**
10295
		 * Check if user eligible to download premium version updates.
10296
		 *
10297
		 * @author Vova Feldman (@svovaf)
10298
		 * @since  1.0.6
10299
		 *
10300
		 * @return bool
10301
		 */
10302
		private function _can_download_premium() {
10303
			return $this->has_active_valid_license() ||
10304
			       ( $this->is_trial() && ! $this->get_trial_plan()->is_free() );
10305
		}
10306
10307
		/**
10308
		 *
10309
		 * @author Vova Feldman (@svovaf)
10310
		 * @since  1.0.6
10311
		 *
10312
		 * @param bool|number $addon_id
10313
		 * @param string      $type "json" or "zip"
10314
		 *
10315
		 * @return string
10316
		 */
10317
		private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) {
10318
10319
			$is_addon = $this->_is_addon_id( $addon_id );
10320
10321
			$is_premium = null;
10322
			if ( ! $is_addon ) {
10323
				$is_premium = $this->_can_download_premium();
10324
			} 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 10317 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...
10325
				$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 10317 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...
10326
			}
10327
10328
			// If add-on, then append add-on ID.
10329
			$endpoint = ( $is_addon ? "/addons/$addon_id" : '' ) .
10330
			            '/updates/latest.' . $type;
10331
10332
			// If add-on and not yet activated, try to fetch based on server licensing.
10333
			if ( is_bool( $is_premium ) ) {
10334
				$endpoint = add_query_arg( 'is_premium', json_encode( $is_premium ), $endpoint );
10335
			}
10336
10337
			if ( $this->has_secret_key() ) {
10338
				$endpoint = add_query_arg( 'type', 'all', $endpoint );
10339
			}
10340
10341
			return $endpoint;
10342
		}
10343
10344
		/**
10345
		 * @author Vova Feldman (@svovaf)
10346
		 * @since  1.0.4
10347
		 *
10348
		 * @param bool|number $addon_id
10349
		 * @param bool        $flush Since 1.1.7.3
10350
		 *
10351
		 * @return object|false Plugin latest tag info.
10352
		 */
10353
		function _fetch_latest_version( $addon_id = false, $flush = 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...
10354
			$this->_logger->entrance();
10355
10356
			/**
10357
			 * @since 1.1.7.3 Check for plugin updates from Freemius only if opted-in.
10358
			 * @since 1.1.7.4 Also check updates for add-ons.
10359
			 */
10360
			if ( ! $this->is_registered() &&
10361
			     ! $this->_is_addon_id( $addon_id )
10362
			) {
10363
				return false;
10364
			}
10365
10366
			$tag = $this->get_api_site_or_plugin_scope()->get(
10367
				$this->_get_latest_version_endpoint( $addon_id, 'json' ),
10368
				$flush
10369
			);
10370
10371
			$latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get';
10372
10373
			$this->_logger->departure( 'Latest version ' . $latest_version );
10374
10375
			return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false;
10376
		}
10377
10378
		#----------------------------------------------------------------------------------
10379
		#region Download Plugin
10380
		#----------------------------------------------------------------------------------
10381
10382
		/**
10383
		 * Download latest plugin version, based on plan.
10384
		 *
10385
		 * Not like _download_latest(), this will redirect the page
10386
		 * to secure download url to prevent dual download (from FS to WP server,
10387
		 * and then from WP server to the client / browser).
10388
		 *
10389
		 * @author Vova Feldman (@svovaf)
10390
		 * @since  1.0.9
10391
		 *
10392
		 * @param bool|number $plugin_id
10393
		 *
10394
		 * @uses   FS_Api
10395
		 * @uses   wp_redirect()
10396
		 */
10397
		private function download_latest_directly( $plugin_id = false ) {
10398
			$this->_logger->entrance();
10399
10400
			wp_redirect( $this->get_latest_download_api_url( $plugin_id ) );
10401
		}
10402
10403
		/**
10404
		 * Get latest plugin FS API download URL.
10405
		 *
10406
		 * @author Vova Feldman (@svovaf)
10407
		 * @since  1.0.9
10408
		 *
10409
		 * @param bool|number $plugin_id
10410
		 *
10411
		 * @return string
10412
		 */
10413
		private function get_latest_download_api_url( $plugin_id = false ) {
10414
			$this->_logger->entrance();
10415
10416
			return $this->get_api_site_scope()->get_signed_url(
10417
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
10418
			);
10419
		}
10420
10421
		/**
10422
		 * Get payment invoice URL.
10423
		 *
10424
		 * @author Vova Feldman (@svovaf)
10425
		 * @since  1.2.0
10426
		 *
10427
		 * @param bool|number $payment_id
10428
		 *
10429
		 * @return string
10430
		 */
10431
		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...
10432
			$this->_logger->entrance();
10433
10434
			return $this->get_api_user_scope()->get_signed_url(
10435
				"/payments/{$payment_id}/invoice.pdf"
10436
			);
10437
		}
10438
10439
		/**
10440
		 * Get latest plugin download link.
10441
		 *
10442
		 * @author Vova Feldman (@svovaf)
10443
		 * @since  1.0.9
10444
		 *
10445
		 * @param string      $label
10446
		 * @param bool|number $plugin_id
10447
		 *
10448
		 * @return string
10449
		 */
10450
		private function get_latest_download_link( $label, $plugin_id = false ) {
10451
			return sprintf(
10452
				'<a target="_blank" href="%s">%s</a>',
10453
				$this->_get_latest_download_local_url( $plugin_id ),
10454
				$label
10455
			);
10456
		}
10457
10458
		/**
10459
		 * Get latest plugin download local URL.
10460
		 *
10461
		 * @author Vova Feldman (@svovaf)
10462
		 * @since  1.0.9
10463
		 *
10464
		 * @param bool|number $plugin_id
10465
		 *
10466
		 * @return string
10467
		 */
10468
		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...
10469
			// Add timestamp to protect from caching.
10470
			$params = array( 'ts' => WP_FS__SCRIPT_START_TIME );
10471
10472
			if ( ! empty( $plugin_id ) ) {
10473
				$params['plugin_id'] = $plugin_id;
10474
			}
10475
10476
			return $this->get_account_url( 'download_latest', $params );
10477
		}
10478
10479
		#endregion Download Plugin ------------------------------------------------------------------
10480
10481
		/**
10482
		 * @author Vova Feldman (@svovaf)
10483
		 * @since  1.0.4
10484
		 *
10485
		 * @uses   FS_Api
10486
		 *
10487
		 * @param bool        $background Hints the method if it's a background updates check. If false, it means that
10488
		 *                                was initiated by the admin.
10489
		 * @param bool|number $plugin_id
10490
		 * @param bool        $flush      Since 1.1.7.3
10491
		 */
10492
		private function check_updates( $background = false, $plugin_id = false, $flush = true ) {
10493
			$this->_logger->entrance();
10494
10495
			// Check if there's a newer version for download.
10496
			$new_version = $this->_fetch_newer_version( $plugin_id, $flush );
10497
10498
			$update = null;
10499
			if ( is_object( $new_version ) ) {
10500
				$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...
10501
10502
				if ( ! $background ) {
10503
					$this->_admin_notices->add(
10504
						sprintf(
10505
							$this->get_text( 'version-x-released' ) . ' ' . $this->get_text( 'please-download-x' ),
10506
							$update->version,
10507
							sprintf(
10508
								'<a href="%s" target="_blank">%s</a>',
10509
								$this->get_account_url( 'download_latest' ),
10510
								sprintf( $this->get_text( 'latest-x-version' ), $this->_site->plan->title )
10511
							)
10512
						),
10513
						$this->get_text( 'new' ) . '!'
10514
					);
10515
				}
10516
			} else if ( false === $new_version && ! $background ) {
10517
				$this->_admin_notices->add(
10518
					$this->get_text( 'you-have-latest' ),
10519
					$this->get_text( 'you-are-good' )
10520
				);
10521
			}
10522
10523
			$this->_store_update( $update, true, $plugin_id );
10524
		}
10525
10526
		/**
10527
		 * @author Vova Feldman (@svovaf)
10528
		 * @since  1.0.4
10529
		 *
10530
		 * @param bool $flush Since 1.1.7.3 add 24 hour cache by default.
10531
		 *
10532
		 * @return FS_Plugin[]
10533
		 *
10534
		 * @uses   FS_Api
10535
		 */
10536
		private function sync_addons( $flush = false ) {
10537
			$this->_logger->entrance();
10538
10539
			$api = $this->get_api_site_or_plugin_scope();
10540
10541
			/**
10542
			 * @since 1.2.1
10543
			 *
10544
			 * If there's a cached version of the add-ons and not asking
10545
			 * for a flush, just use the currently stored add-ons.
10546
			 */
10547
			if ( ! $flush && $api->is_cached( '/addons.json?enriched=true' ) ) {
10548
				$addons = self::get_all_addons();
10549
10550
				return $addons[ $this->_plugin->id ];
10551
			}
10552
10553
			$result = $api->get( '/addons.json?enriched=true', $flush );
10554
10555
			$addons = array();
10556
			if ( $this->is_api_result_object( $result, 'plugins' ) &&
10557
			     is_array( $result->plugins )
10558
			) {
10559
				for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) {
10560
					$addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] );
10561
				}
10562
10563
				$this->_store_addons( $addons, true );
10564
			}
10565
10566
			return $addons;
10567
		}
10568
10569
		/**
10570
		 * Handle user email update.
10571
		 *
10572
		 * @author Vova Feldman (@svovaf)
10573
		 * @since  1.0.3
10574
		 * @uses   FS_Api
10575
		 *
10576
		 * @param string $new_email
10577
		 *
10578
		 * @return object
10579
		 */
10580
		private function update_email( $new_email ) {
10581
			$this->_logger->entrance();
10582
10583
10584
			$api  = $this->get_api_user_scope();
10585
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array(
10586
				'email'                   => $new_email,
10587
				'after_email_confirm_url' => $this->_get_admin_page_url(
10588
					'account',
10589
					array( 'fs_action' => 'sync_user' )
10590
				),
10591
			) );
10592
10593
			if ( ! isset( $user->error ) ) {
10594
				$this->_user->email       = $user->email;
10595
				$this->_user->is_verified = $user->is_verified;
10596
				$this->_store_user();
10597
			} 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...
10598
				// handle different error cases.
10599
10600
			}
10601
10602
			return $user;
10603
		}
10604
10605
		#----------------------------------------------------------------------------------
10606
		#region API Error Handling
10607
		#----------------------------------------------------------------------------------
10608
10609
		/**
10610
		 * @author Vova Feldman (@svovaf)
10611
		 * @since  1.1.1
10612
		 *
10613
		 * @param mixed $result
10614
		 *
10615
		 * @return bool Is API result contains an error.
10616
		 */
10617
		private function is_api_error( $result ) {
10618
			return FS_Api::is_api_error( $result );
10619
		}
10620
10621
		/**
10622
		 * Checks if given API result is a non-empty and not an error object.
10623
		 *
10624
		 * @author Vova Feldman (@svovaf)
10625
		 * @since  1.2.1.5
10626
		 *
10627
		 * @param mixed       $result
10628
		 * @param string|null $required_property Optional property we want to verify that is set.
10629
		 *
10630
		 * @return bool
10631
		 */
10632
		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...
10633
			return FS_Api::is_api_result_object( $result, $required_property );
10634
		}
10635
10636
		/**
10637
		 * Checks if given API result is a non-empty entity object with non-empty ID.
10638
		 *
10639
		 * @author Vova Feldman (@svovaf)
10640
		 * @since  1.2.1.5
10641
		 *
10642
		 * @param mixed $result
10643
		 *
10644
		 * @return bool
10645
		 */
10646
		private function is_api_result_entity( $result ) {
10647
			return FS_Api::is_api_result_entity( $result );
10648
		}
10649
10650
		#endregion
10651
10652
		/**
10653
		 * Make sure a given argument is an array of a specific type.
10654
		 *
10655
		 * @author Vova Feldman (@svovaf)
10656
		 * @since  1.2.1.5
10657
		 *
10658
		 * @param mixed  $array
10659
		 * @param string $class
10660
		 *
10661
		 * @return bool
10662
		 */
10663
		private function is_array_instanceof( $array, $class ) {
10664
			return ( is_array( $array ) && ( empty( $array ) || $array[0] instanceof $class ) );
10665
		}
10666
10667
		/**
10668
		 * Start install ownership change.
10669
		 *
10670
		 * @author Vova Feldman (@svovaf)
10671
		 * @since  1.1.1
10672
		 * @uses   FS_Api
10673
		 *
10674
		 * @param string $candidate_email
10675
		 *
10676
		 * @return bool Is ownership change successfully initiated.
10677
		 */
10678
		private function init_change_owner( $candidate_email ) {
10679
			$this->_logger->entrance();
10680
10681
			$api    = $this->get_api_site_scope();
10682
			$result = $api->call( "/users/{$this->_user->id}.json", 'put', array(
10683
				'email'             => $candidate_email,
10684
				'after_confirm_url' => $this->_get_admin_page_url(
10685
					'account',
10686
					array( 'fs_action' => 'change_owner' )
10687
				),
10688
			) );
10689
10690
			return ! $this->is_api_error( $result );
10691
		}
10692
10693
		/**
10694
		 * Handle install ownership change.
10695
		 *
10696
		 * @author Vova Feldman (@svovaf)
10697
		 * @since  1.1.1
10698
		 * @uses   FS_Api
10699
		 *
10700
		 * @return bool Was ownership change successfully complete.
10701
		 */
10702
		private function complete_change_owner() {
10703
			$this->_logger->entrance();
10704
10705
			$site_result = $this->get_api_site_scope( true )->get();
10706
			$site        = new FS_Site( $site_result );
10707
			$this->_site = $site;
10708
10709
			$user     = new FS_User();
10710
			$user->id = fs_request_get( 'user_id' );
10711
10712
			// Validate install's user and given user.
10713
			if ( $user->id != $this->_site->user_id ) {
10714
				return false;
10715
			}
10716
10717
			$user->public_key = fs_request_get( 'user_public_key' );
10718
			$user->secret_key = fs_request_get( 'user_secret_key' );
10719
10720
			// Fetch new user information.
10721
			$this->_user = $user;
10722
			$user_result = $this->get_api_user_scope( true )->get();
10723
			$user        = new FS_User( $user_result );
10724
			$this->_user = $user;
10725
10726
			$this->_set_account( $user, $site );
10727
10728
			return true;
10729
		}
10730
10731
		/**
10732
		 * Handle user name update.
10733
		 *
10734
		 * @author Vova Feldman (@svovaf)
10735
		 * @since  1.0.9
10736
		 * @uses   FS_Api
10737
		 *
10738
		 * @return object
10739
		 */
10740
		private function update_user_name() {
10741
			$this->_logger->entrance();
10742
			$name = fs_request_get( 'fs_user_name_' . $this->_slug, '' );
10743
10744
			$api  = $this->get_api_user_scope();
10745
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array(
10746
				'name' => $name,
10747
			) );
10748
10749
			if ( ! isset( $user->error ) ) {
10750
				$this->_user->first = $user->first;
10751
				$this->_user->last  = $user->last;
10752
				$this->_store_user();
10753
			} 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...
10754
				// handle different error cases.
10755
10756
			}
10757
10758
			return $user;
10759
		}
10760
10761
		/**
10762
		 * Verify user email.
10763
		 *
10764
		 * @author Vova Feldman (@svovaf)
10765
		 * @since  1.0.3
10766
		 * @uses   FS_Api
10767
		 */
10768
		private function verify_email() {
10769
			$this->_handle_account_user_sync();
10770
10771
			if ( $this->_user->is_verified() ) {
10772
				return;
10773
			}
10774
10775
			$api    = $this->get_api_site_scope();
10776
			$result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array(
10777
				'after_email_confirm_url' => $this->_get_admin_page_url(
10778
					'account',
10779
					array( 'fs_action' => 'sync_user' )
10780
				)
10781
			) );
10782
10783
			if ( ! isset( $result->error ) ) {
10784
				$this->_admin_notices->add( sprintf(
10785
					$this->get_text( 'verification-email-sent-message' ),
10786
					sprintf( '<a href="mailto:%1s">%2s</a>', esc_url( $this->_user->email ), $this->_user->email )
10787
				) );
10788
			} 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...
10789
				// handle different error cases.
10790
10791
			}
10792
		}
10793
10794
		/**
10795
		 * @author Vova Feldman (@svovaf)
10796
		 * @since  1.1.2
10797
		 *
10798
		 * @param array $params
10799
		 *
10800
		 * @return string
10801
		 */
10802
		private function get_activation_url( $params = array() ) {
10803
			if ( $this->is_addon() ) {
10804
				/**
10805
				 * @author Vova Feldman (@svovaf)
10806
				 * @since  1.2.1.7 Add-on's activation is the parent's module activation.
10807
				 */
10808
				return $this->get_parent_instance()->get_activation_url( $params );
10809
			}
10810
10811
			return $this->apply_filters( 'connect_url', $this->_get_admin_page_url( '', $params ) );
10812
		}
10813
10814
		/**
10815
		 * @author Vova Feldman (@svovaf)
10816
		 * @since  1.2.1.5
10817
		 *
10818
		 * @param array $params
10819
		 *
10820
		 * @return string
10821
		 */
10822
		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...
10823
			$params['fs_action'] = 'reset_anonymous_mode';
10824
			$params['fs_slug']   = $this->_slug;
10825
10826
			return $this->apply_filters( 'connect_url', $this->_get_admin_page_url( '', $params ) );
10827
		}
10828
10829
		/**
10830
		 * Get the URL of the page that should be loaded after the user connect or skip in the opt-in screen.
10831
		 *
10832
		 * @author Vova Feldman (@svovaf)
10833
		 * @since  1.1.3
10834
		 *
10835
		 * @param string $filter Filter name.
10836
		 *
10837
		 * @return string
10838
		 */
10839
		function get_after_activation_url( $filter ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10840
			$first_time_path = $this->_menu->get_first_time_path();
10841
10842
			return $this->apply_filters(
10843
				$filter,
10844
				empty( $first_time_path ) ?
10845
					$this->_get_admin_page_url() :
10846
					$first_time_path
10847
			);
10848
		}
10849
10850
		/**
10851
		 * Handle account page updates / edits / actions.
10852
		 *
10853
		 * @author Vova Feldman (@svovaf)
10854
		 * @since  1.0.2
10855
		 *
10856
		 */
10857
		private function _handle_account_edits() {
10858
			if ( ! $this->is_user_admin() ) {
10859
				return;
10860
			}
10861
10862
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
10863
			$action    = fs_get_action();
10864
10865
			switch ( $action ) {
10866
				case 'delete_account':
10867
					check_admin_referer( $action );
10868
10869
					if ( $plugin_id == $this->get_id() ) {
10870
						$this->delete_account_event();
10871
10872
						// Clear user and site.
10873
						$this->_site = null;
10874
						$this->_user = null;
10875
10876
						fs_redirect( $this->get_activation_url() );
10877
					} else {
10878
						if ( $this->is_addon_activated( $plugin_id ) ) {
10879
							$fs_addon = self::get_instance_by_id( $plugin_id );
10880
							$fs_addon->delete_account_event();
10881
10882
							fs_redirect( $this->_get_admin_page_url( 'account' ) );
10883
						}
10884
					}
10885
10886
					return;
10887
10888
				case 'downgrade_account':
10889
					check_admin_referer( $action );
10890
10891
					if ( $plugin_id == $this->get_id() ) {
10892
						$this->_downgrade_site();
10893
					} else if ( $this->is_addon_activated( $plugin_id ) ) {
10894
						$fs_addon = self::get_instance_by_id( $plugin_id );
10895
						$fs_addon->_downgrade_site();
10896
					}
10897
10898
					return;
10899
10900
				case 'activate_license':
10901
					check_admin_referer( $action );
10902
10903
					if ( $plugin_id == $this->get_id() ) {
10904
						$this->_activate_license();
10905
					} else {
10906
						if ( $this->is_addon_activated( $plugin_id ) ) {
10907
							$fs_addon = self::get_instance_by_id( $plugin_id );
10908
							$fs_addon->_activate_license();
10909
						}
10910
					}
10911
10912
					return;
10913
10914
				case 'deactivate_license':
10915
					check_admin_referer( $action );
10916
10917
					if ( $plugin_id == $this->get_id() ) {
10918
						$this->_deactivate_license();
10919
					} else {
10920
						if ( $this->is_addon_activated( $plugin_id ) ) {
10921
							$fs_addon = self::get_instance_by_id( $plugin_id );
10922
							$fs_addon->_deactivate_license();
10923
						}
10924
					}
10925
10926
					return;
10927
10928
				case 'check_updates':
10929
					check_admin_referer( $action );
10930
					$this->check_updates();
10931
10932
					return;
10933
10934
				case 'change_owner':
10935
					$state = fs_request_get( 'state', 'init' );
10936
					switch ( $state ) {
10937
						case 'init':
10938
							$candidate_email = fs_request_get( 'candidate_email', '' );
10939
10940
							if ( $this->init_change_owner( $candidate_email ) ) {
10941
								$this->_admin_notices->add( sprintf( $this->get_text( 'change-owner-request-sent-x' ), '<b>' . $this->_user->email . '</b>' ) );
10942
							}
10943
							break;
10944
						case 'owner_confirmed':
10945
							$candidate_email = fs_request_get( 'candidate_email', '' );
10946
10947
							$this->_admin_notices->add( sprintf( $this->get_text( 'change-owner-request_owner-confirmed' ), '<b>' . $candidate_email . '</b>' ) );
10948
							break;
10949
						case 'candidate_confirmed':
10950
							if ( $this->complete_change_owner() ) {
10951
								$this->_admin_notices->add_sticky(
10952
									sprintf( $this->get_text( 'change-owner-request_candidate-confirmed' ), '<b>' . $this->_user->email . '</b>' ),
10953
									'ownership_changed',
10954
									$this->get_text( 'congrats' ) . '!'
10955
								);
10956
							} 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...
10957
								// @todo Handle failed ownership change message.
10958
							}
10959
							break;
10960
					}
10961
10962
					return;
10963
10964
				case 'update_email':
10965
					check_admin_referer( 'update_email' );
10966
10967
					$new_email = fs_request_get( 'fs_email_' . $this->_slug, '' );
10968
					$result    = $this->update_email( $new_email );
10969
10970
					if ( isset( $result->error ) ) {
10971
						switch ( $result->error->code ) {
10972
							case 'user_exist':
10973
								$this->_admin_notices->add(
10974
									$this->get_text( 'user-exist-message' ) . ' ' .
10975
									sprintf( $this->get_text( 'user-exist-message_ownership' ), '<b>' . $new_email . '</b>' ) .
10976
									sprintf(
10977
										'<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
10978
										$this->get_account_url( 'change_owner', array(
10979
											'state'           => 'init',
10980
											'candidate_email' => $new_email
10981
										) ),
10982
										$this->get_text( 'change-ownership' )
10983
									),
10984
									$this->get_text( 'oops' ) . '...',
10985
									'error'
10986
								);
10987
								break;
10988
						}
10989
					} else {
10990
						$this->_admin_notices->add( $this->get_text( 'email-updated-message' ) );
10991
					}
10992
10993
					return;
10994
10995
				case 'update_user_name':
10996
					check_admin_referer( 'update_user_name' );
10997
10998
					$result = $this->update_user_name();
10999
11000
					if ( isset( $result->error ) ) {
11001
						$this->_admin_notices->add(
11002
							$this->get_text( 'name-update-failed-message' ),
11003
							$this->get_text( 'oops' ) . '...',
11004
							'error'
11005
						);
11006
					} else {
11007
						$this->_admin_notices->add( $this->get_text( 'name-updated-message' ) );
11008
					}
11009
11010
					return;
11011
11012
				#region Actions that might be called from external links (e.g. email)
11013
11014
				case 'cancel_trial':
11015
					if ( $plugin_id == $this->get_id() ) {
11016
						$this->_cancel_trial();
11017
					} else {
11018
						if ( $this->is_addon_activated( $plugin_id ) ) {
11019
							$fs_addon = self::get_instance_by_id( $plugin_id );
11020
							$fs_addon->_cancel_trial();
11021
						}
11022
					}
11023
11024
					return;
11025
11026
				case 'verify_email':
11027
					$this->verify_email();
11028
11029
					return;
11030
11031
				case 'sync_user':
11032
					$this->_handle_account_user_sync();
11033
11034
					return;
11035
11036
				case $this->_slug . '_sync_license':
11037
					$this->_sync_license();
11038
11039
					return;
11040
11041
				case 'download_latest':
11042
					$this->download_latest_directly( $plugin_id );
11043
11044
					return;
11045
11046
				#endregion
11047
			}
11048
11049
			if ( WP_FS__IS_POST_REQUEST ) {
11050
				$properties = array( 'site_secret_key', 'site_id', 'site_public_key' );
11051
				foreach ( $properties as $p ) {
11052
					if ( 'update_' . $p === $action ) {
11053
						check_admin_referer( $action );
11054
11055
						$this->_logger->log( $action );
11056
11057
						$site_property                      = substr( $p, strlen( 'site_' ) );
11058
						$site_property_value                = fs_request_get( 'fs_' . $p . '_' . $this->_slug, '' );
11059
						$this->get_site()->{$site_property} = $site_property_value;
11060
11061
						// Store account after modification.
11062
						$this->_store_site();
11063
11064
						$this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value );
11065
11066
						$this->_admin_notices->add( sprintf(
11067
							$this->get_text( 'x-updated' ),
11068
							'<b>' . str_replace( '_', ' ', $p ) . '</b>' ) );
11069
11070
						return;
11071
					}
11072
				}
11073
			}
11074
		}
11075
11076
		/**
11077
		 * Account page resources load.
11078
		 *
11079
		 * @author Vova Feldman (@svovaf)
11080
		 * @since  1.0.6
11081
		 */
11082
		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...
11083
			$this->_logger->entrance();
11084
11085
			$this->_logger->info( var_export( $_REQUEST, true ) );
11086
11087
			fs_enqueue_local_style( 'fs_account', '/admin/account.css' );
11088
11089
			if ( $this->has_addons() ) {
11090
				wp_enqueue_script( 'plugin-install' );
11091
				add_thickbox();
11092
11093
				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...
11094
					$classes .= ' plugins-php';
11095
11096
					return $classes;
11097
				}
11098
11099
				add_filter( 'admin_body_class', 'fs_addons_body_class' );
11100
			}
11101
11102
			if ( $this->has_paid_plan() &&
11103
			     ! $this->has_any_license() &&
11104
			     ! $this->is_sync_executed() &&
11105
			     $this->is_tracking_allowed()
11106
			) {
11107
				/**
11108
				 * If no licenses found and no sync job was executed during the last 24 hours,
11109
				 * just execute the sync job right away (blocking execution).
11110
				 *
11111
				 * @since 1.1.7.3
11112
				 */
11113
				$this->run_manual_sync();
11114
			}
11115
11116
			$this->_handle_account_edits();
11117
11118
			$this->do_action( 'account_page_load_before_departure' );
11119
		}
11120
11121
		/**
11122
		 * Render account page.
11123
		 *
11124
		 * @author Vova Feldman (@svovaf)
11125
		 * @since  1.0.0
11126
		 */
11127
		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...
11128
			$this->_logger->entrance();
11129
11130
			$template = 'account.php';
11131
			if ( 'billing' === fs_request_get( 'tab' ) ) {
11132
				$template = 'billing.php';
11133
			}
11134
11135
			$vars = array( 'slug' => $this->_slug );
11136
11137
			/**
11138
			 * Added filter to the template to allow developers wrapping the template
11139
			 * in custom HTML (e.g. within a wizard/tabs).
11140
			 *
11141
			 * @author Vova Feldman (@svovaf)
11142
			 * @since  1.2.1.6
11143
			 */
11144
			echo $this->apply_filters( "templates/{$template}", fs_get_template( $template, $vars ) );
11145
		}
11146
11147
		/**
11148
		 * Render account connect page.
11149
		 *
11150
		 * @author Vova Feldman (@svovaf)
11151
		 * @since  1.0.7
11152
		 */
11153
		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...
11154
			$this->_logger->entrance();
11155
11156
			$vars = array( 'slug' => $this->_slug );
11157
11158
			/**
11159
			 * Added filter to the template to allow developers wrapping the template
11160
			 * in custom HTML (e.g. within a wizard/tabs).
11161
			 *
11162
			 * @author Vova Feldman (@svovaf)
11163
			 * @since  1.2.1.6
11164
			 */
11165
			echo $this->apply_filters( 'templates/connect.php', fs_get_template( 'connect.php', $vars ) );
11166
		}
11167
11168
		/**
11169
		 * Load required resources before add-ons page render.
11170
		 *
11171
		 * @author Vova Feldman (@svovaf)
11172
		 * @since  1.0.6
11173
		 */
11174
		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...
11175
			$this->_logger->entrance();
11176
11177
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
11178
11179
			wp_enqueue_script( 'plugin-install' );
11180
			add_thickbox();
11181
11182
			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 (L11093-11097) 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...
11183
				$classes .= ' plugins-php';
11184
11185
				return $classes;
11186
			}
11187
11188
			add_filter( 'admin_body_class', 'fs_addons_body_class' );
11189
11190
			if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) {
11191
				$this->_admin_notices->add(
11192
					sprintf( $this->get_text( 'addons-info-external-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
11193
					$this->get_text( 'heads-up' ),
11194
					'update-nag'
11195
				);
11196
			}
11197
		}
11198
11199
		/**
11200
		 * Render add-ons page.
11201
		 *
11202
		 * @author Vova Feldman (@svovaf)
11203
		 * @since  1.0.6
11204
		 */
11205
		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...
11206
			$this->_logger->entrance();
11207
11208
			$vars = array( 'slug' => $this->_slug );
11209
11210
			/**
11211
			 * Added filter to the template to allow developers wrapping the template
11212
			 * in custom HTML (e.g. within a wizard/tabs).
11213
			 *
11214
			 * @author Vova Feldman (@svovaf)
11215
			 * @since  1.2.1.6
11216
			 */
11217
			echo $this->apply_filters( 'templates/add-ons.php', fs_get_template( 'add-ons.php', $vars ) );
11218
		}
11219
11220
		/* Pricing & Upgrade
11221
		------------------------------------------------------------------------------------------------------------------*/
11222
		/**
11223
		 * Render pricing page.
11224
		 *
11225
		 * @author Vova Feldman (@svovaf)
11226
		 * @since  1.0.0
11227
		 */
11228
		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...
11229
			$this->_logger->entrance();
11230
11231
			$vars = array( 'slug' => $this->_slug );
11232
11233
			if ( 'true' === fs_request_get( 'checkout', false ) ) {
11234
				fs_require_once_template( 'checkout.php', $vars );
11235
			} else {
11236
				fs_require_once_template( 'pricing.php', $vars );
11237
			}
11238
		}
11239
11240
		#----------------------------------------------------------------------------------
11241
		#region Contact Us
11242
		#----------------------------------------------------------------------------------
11243
11244
		/**
11245
		 * Render contact-us page.
11246
		 *
11247
		 * @author Vova Feldman (@svovaf)
11248
		 * @since  1.0.3
11249
		 */
11250
		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...
11251
			$this->_logger->entrance();
11252
11253
			$vars = array( 'slug' => $this->_slug );
11254
			fs_require_once_template( 'contact.php', $vars );
11255
		}
11256
11257
		#endregion ------------------------------------------------------------------------
11258
11259
		/**
11260
		 * Hide all admin notices to prevent distractions.
11261
		 *
11262
		 * @author Vova Feldman (@svovaf)
11263
		 * @since  1.0.3
11264
		 *
11265
		 * @uses   remove_all_actions()
11266
		 */
11267
		private static function _hide_admin_notices() {
11268
			remove_all_actions( 'admin_notices' );
11269
			remove_all_actions( 'network_admin_notices' );
11270
			remove_all_actions( 'all_admin_notices' );
11271
			remove_all_actions( 'user_admin_notices' );
11272
		}
11273
11274
		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...
11275
			self::_hide_admin_notices();
11276
11277
			// Hide footer.
11278
			echo '<style>#wpfooter { display: none !important; }</style>';
11279
		}
11280
11281
		/**
11282
		 * Attach to admin_head hook to hide all admin notices.
11283
		 *
11284
		 * @author Vova Feldman (@svovaf)
11285
		 * @since  1.0.3
11286
		 */
11287
		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...
11288
			add_action( 'admin_head', 'Freemius::_clean_admin_content_section_hook' );
11289
		}
11290
11291
		/* CSS & JavaScript
11292
		------------------------------------------------------------------------------------------------------------------*/
11293
		/*		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...
11294
					$url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src );
11295
11296
					$this->_logger->entrance( 'script = ' . $url );
11297
11298
					wp_enqueue_script( $handle, $url );
11299
				}*/
11300
11301
		/* SDK
11302
		------------------------------------------------------------------------------------------------------------------*/
11303
		private $_user_api;
11304
11305
		/**
11306
		 *
11307
		 * @author Vova Feldman (@svovaf)
11308
		 * @since  1.0.2
11309
		 *
11310
		 * @param bool $flush
11311
		 *
11312
		 * @return FS_Api
11313
		 */
11314
		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...
11315
			if ( ! isset( $this->_user_api ) || $flush ) {
11316
				$this->_user_api = FS_Api::instance(
11317
					$this->_slug,
11318
					'user',
11319
					$this->_user->id,
11320
					$this->_user->public_key,
11321
					! $this->is_live(),
11322
					$this->_user->secret_key
11323
				);
11324
			}
11325
11326
			return $this->_user_api;
11327
		}
11328
11329
		private $_site_api;
11330
11331
		/**
11332
		 *
11333
		 * @author Vova Feldman (@svovaf)
11334
		 * @since  1.0.2
11335
		 *
11336
		 * @param bool $flush
11337
		 *
11338
		 * @return FS_Api
11339
		 */
11340
		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...
11341
			if ( ! isset( $this->_site_api ) || $flush ) {
11342
				$this->_site_api = FS_Api::instance(
11343
					$this->_slug,
11344
					'install',
11345
					$this->_site->id,
11346
					$this->_site->public_key,
11347
					! $this->is_live(),
11348
					$this->_site->secret_key
11349
				);
11350
			}
11351
11352
			return $this->_site_api;
11353
		}
11354
11355
		private $_plugin_api;
11356
11357
		/**
11358
		 * Get plugin public API scope.
11359
		 *
11360
		 * @author Vova Feldman (@svovaf)
11361
		 * @since  1.0.7
11362
		 *
11363
		 * @return FS_Api
11364
		 */
11365
		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...
11366
			if ( ! isset( $this->_plugin_api ) ) {
11367
				$this->_plugin_api = FS_Api::instance(
11368
					$this->_slug,
11369
					'plugin',
11370
					$this->_plugin->id,
11371
					$this->_plugin->public_key,
11372
					! $this->is_live()
11373
				);
11374
			}
11375
11376
			return $this->_plugin_api;
11377
		}
11378
11379
		/**
11380
		 * Get site API scope object (fallback to public plugin scope when not registered).
11381
		 *
11382
		 * @author Vova Feldman (@svovaf)
11383
		 * @since  1.0.7
11384
		 *
11385
		 * @return FS_Api
11386
		 */
11387
		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...
11388
			return $this->is_registered() ?
11389
				$this->get_api_site_scope() :
11390
				$this->get_api_plugin_scope();
11391
		}
11392
11393
		/**
11394
		 * Show trial promotional notice (if any trial exist).
11395
		 *
11396
		 * @author Vova Feldman (@svovaf)
11397
		 * @since  1.0.9
11398
		 *
11399
		 * @param $plans
11400
		 */
11401
		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...
11402
			$this->_storage->has_trial_plan = FS_Plan_Manager::instance()->has_trial_plan( $plans );
11403
		}
11404
11405
		/**
11406
		 * During trial promotion the "upgrade" submenu item turns to
11407
		 * "start trial" to encourage the trial. Since we want to keep
11408
		 * the same menu item handler and there's no robust way to
11409
		 * add new arguments to the menu item link's querystring,
11410
		 * use JavaScript to find the menu item and update the href of
11411
		 * the link.
11412
		 *
11413
		 * @author Vova Feldman (@svovaf)
11414
		 * @since  1.2.1.5
11415
		 */
11416
		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...
11417
			$template_args = array( 'slug' => $this->_slug );
11418
			fs_require_template( 'add-trial-to-pricing.php', $template_args );
11419
		}
11420
11421
		/**
11422
		 * Show trial promotional notice (if any trial exist).
11423
		 *
11424
		 * @author Vova Feldman (@svovaf)
11425
		 * @since  1.0.9
11426
		 *
11427
		 * @return bool If trial notice added.
11428
		 */
11429
		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...
11430
			if ( ! $this->is_user_admin() ) {
11431
				return false;
11432
			}
11433
11434
			if ( ! $this->is_user_in_admin() ) {
11435
				return false;
11436
			}
11437
11438
			// Check if trial message is already shown.
11439
			if ( $this->_admin_notices->has_sticky( 'trial_promotion' ) ) {
11440
				add_action( 'admin_footer', array( &$this, '_fix_start_trial_menu_item_url' ) );
11441
11442
				$this->_menu->add_counter_to_menu_item( 1, 'fs-trial' );
11443
11444
				return false;
11445
			}
11446
11447
			if ( $this->is_premium() && ! WP_FS__DEV_MODE ) {
11448
				// Don't show trial if running the premium code, unless running in DEV mode.
11449
				return false;
11450
			}
11451
11452
			if ( ! $this->has_trial_plan() ) {
11453
				// No plans with trial.
11454
				return false;
11455
			}
11456
11457
			if ( ! $this->apply_filters( 'show_trial', true ) ) {
11458
				// Developer explicitly asked not to show the trial promo.
11459
				return false;
11460
			}
11461
11462
			if ( $this->is_registered() ) {
11463
				// Check if trial already utilized.
11464
				if ( $this->_site->is_trial_utilized() ) {
11465
					return false;
11466
				}
11467
11468
				if ( $this->is_paying_or_trial() ) {
11469
					// Don't show trial if paying or already in trial.
11470
					return false;
11471
				}
11472
			}
11473
11474
			if ( $this->is_activation_mode() || $this->is_pending_activation() ) {
11475
				// If not yet opted-in/skipped, or pending activation, don't show trial.
11476
				return false;
11477
			}
11478
11479
			$last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false );
11480
			$was_promotion_shown_before      = ( false !== $last_time_trial_promotion_shown );
11481
11482
			// Show promotion if never shown before and 24 hours after initial activation with FS.
11483
			if ( ! $was_promotion_shown_before &&
11484
			     $this->_storage->install_timestamp > ( time() - WP_FS__TIME_24_HOURS_IN_SEC )
11485
			) {
11486
				return false;
11487
			}
11488
11489
			// OR if promotion was shown before, try showing it every 30 days.
11490
			if ( $was_promotion_shown_before &&
11491
			     30 * WP_FS__TIME_24_HOURS_IN_SEC > time() - $last_time_trial_promotion_shown
11492
			) {
11493
				return false;
11494
			}
11495
11496
			$trial_period    = $this->_trial_days;
11497
			$require_payment = $this->_is_trial_require_payment;
11498
			$trial_url       = $this->get_trial_url();
11499
			$plans_string    = strtolower( $this->get_text( 'awesome' ) );
11500
11501
			if ( $this->is_registered() ) {
11502
				// If opted-in, override trial with up to date data from API.
11503
				$trial_plans       = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
11504
				$trial_plans_count = count( $trial_plans );
11505
11506
				if ( 0 === $trial_plans_count ) {
11507
					// If there's no plans with a trial just exit.
11508
					return false;
11509
				}
11510
11511
				/**
11512
				 * @var FS_Plugin_Plan $paid_plan
11513
				 */
11514
				$paid_plan       = $trial_plans[0];
11515
				$require_payment = $paid_plan->is_require_subscription;
11516
				$trial_period    = $paid_plan->trial_period;
11517
11518
				$total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 );
11519
11520
				if ( $total_paid_plans !== $trial_plans_count ) {
11521
					// Not all paid plans have a trial - generate a string of those that have it.
11522
					for ( $i = 0; $i < $trial_plans_count; $i ++ ) {
11523
						$plans_string .= sprintf(
11524
							'<a href="%s">%s</a>',
11525
							$trial_url,
11526
							$trial_plans[ $i ]->title
11527
						);
11528
11529
						if ( $i < $trial_plans_count - 2 ) {
11530
							$plans_string .= ', ';
11531
						} else if ( $i == $trial_plans_count - 2 ) {
11532
							$plans_string .= ' and ';
11533
						}
11534
					}
11535
				}
11536
			}
11537
11538
			$message = sprintf(
11539
				$this->get_text( 'hey' ) . '! ' . $this->get_text( 'trial-x-promotion-message' ),
11540
				sprintf( '<b>%s</b>', $this->get_plugin_name() ),
11541
				$plans_string,
11542
				$trial_period
11543
			);
11544
11545
			// "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...
11546
			$cc_string = $require_payment ?
11547
				sprintf( $this->get_text( 'no-commitment-for-x-days' ), $trial_period ) :
11548
				$this->get_text( 'no-cc-required' ) . '!';
11549
11550
11551
			// Start trial button.
11552
			$button = ' ' . sprintf(
11553
					'<a style="margin-left: 10px; vertical-align: super;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
11554
					$trial_url,
11555
					$this->get_text( 'start-free-trial' )
11556
				);
11557
11558
			$this->_admin_notices->add_sticky(
11559
				$this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ),
11560
				'trial_promotion',
11561
				'',
11562
				'promotion'
11563
			);
11564
11565
			$this->_storage->trial_promotion_shown = WP_FS__SCRIPT_START_TIME;
11566
11567
			return true;
11568
		}
11569
11570
		/**
11571
		 * @author Vova Feldman (@svovaf)
11572
		 * @since  1.2.1.5
11573
		 */
11574
		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...
11575
			if ( $this->has_paid_plan() && ! $this->is_paying() ) {
11576
				// Add basic CSS for admin-notices and menu-item colors.
11577
				fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
11578
			}
11579
		}
11580
11581
		/* Action Links
11582
		------------------------------------------------------------------------------------------------------------------*/
11583
		private $_action_links_hooked = false;
11584
		private $_action_links = array();
11585
11586
		/**
11587
		 * Hook to plugin action links filter.
11588
		 *
11589
		 * @author Vova Feldman (@svovaf)
11590
		 * @since  1.0.0
11591
		 */
11592
		private function hook_plugin_action_links() {
11593
			$this->_logger->entrance();
11594
11595
			$this->_action_links_hooked = true;
11596
11597
			$this->_logger->log( 'Adding action links hooks.' );
11598
11599
			// Add action link to settings page.
11600
			add_filter( 'plugin_action_links_' . $this->_plugin_basename, array(
11601
				&$this,
11602
				'_modify_plugin_action_links_hook'
11603
			), WP_FS__DEFAULT_PRIORITY, 2 );
11604
			add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array(
11605
				&$this,
11606
				'_modify_plugin_action_links_hook'
11607
			), WP_FS__DEFAULT_PRIORITY, 2 );
11608
		}
11609
11610
		/**
11611
		 * Add plugin action link.
11612
		 *
11613
		 * @author Vova Feldman (@svovaf)
11614
		 * @since  1.0.0
11615
		 *
11616
		 * @param      $label
11617
		 * @param      $url
11618
		 * @param bool $external
11619
		 * @param int  $priority
11620
		 * @param bool $key
11621
		 */
11622
		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...
11623
			$this->_logger->entrance();
11624
11625
			if ( ! isset( $this->_action_links[ $priority ] ) ) {
11626
				$this->_action_links[ $priority ] = array();
11627
			}
11628
11629
			if ( false === $key ) {
11630
				$key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) );
11631
			}
11632
11633
			$this->_action_links[ $priority ][] = array(
11634
				'label'    => $label,
11635
				'href'     => $url,
11636
				'key'      => $key,
11637
				'external' => $external
11638
			);
11639
		}
11640
11641
		/**
11642
		 * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection.
11643
		 *
11644
		 * @author Vova Feldman (@svovaf)
11645
		 * @since  1.0.0
11646
		 */
11647
		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...
11648
			$this->_logger->entrance();
11649
11650
			if ( $this->is_registered() ) {
11651
				if ( ! $this->is_paying() && $this->has_paid_plan() ) {
11652
					$this->add_plugin_action_link(
11653
						$this->get_text( 'upgrade' ),
11654
						$this->get_upgrade_url(),
11655
						false,
11656
						7,
11657
						'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...
11658
					);
11659
				}
11660
11661
				if ( $this->has_addons() ) {
11662
					$this->add_plugin_action_link(
11663
						$this->get_text( 'add-ons' ),
11664
						$this->_get_admin_page_url( 'addons' ),
11665
						false,
11666
						9,
11667
						'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...
11668
					);
11669
				}
11670
			}
11671
		}
11672
11673
		/**
11674
		 * Adds "Activate License" or "Change License" link to the main Plugins page link actions collection.
11675
		 *
11676
		 * @author Leo Fajardo (@leorw)
11677
		 * @since  1.1.9
11678
		 */
11679
		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...
11680
			$this->_logger->entrance();
11681
11682
			if ( $this->is_free_plan() && $this->is_addon() ) {
11683
				return;
11684
			}
11685
11686
			if ( ! self::is_ajax() ) {
11687
				// Inject license activation dialog UI and client side code.
11688
				add_action( 'admin_footer', array( &$this, '_add_license_activation_dialog_box' ) );
11689
			}
11690
11691
			$link_text = $this->get_text(
11692
				$this->is_free_plan() ?
11693
					'activate-license' :
11694
					'change-license'
11695
			);
11696
11697
			$this->add_plugin_action_link(
11698
				$link_text,
11699
				'#',
11700
				false,
11701
				11,
11702
				( 'activate-license ' . $this->_slug )
0 ignored issues
show
Documentation introduced by
'activate-license ' . $this->_slug 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...
11703
			);
11704
		}
11705
11706
		/**
11707
		 * Adds "Opt in" or "Opt out" link to the main "Plugins" page link actions collection.
11708
		 *
11709
		 * @author Leo Fajardo (@leorw)
11710
		 * @since  1.2.1.5
11711
		 */
11712
		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...
11713
			if ( ! current_user_can( 'activate_plugins' ) ) {
11714
				return;
11715
			}
11716
11717
			$this->_logger->entrance();
11718
11719
			if ( ! $this->is_plugins_page() ) {
11720
				// Only show tracking links on the plugin's page.
11721
				return;
11722
			}
11723
11724
			if ( ! $this->is_enable_anonymous() ) {
11725
				// Don't allow to opt-out if anonymous mode is disabled.
11726
				return;
11727
			}
11728
11729
			if ( ! $this->is_free_plan() ) {
11730
				// Don't allow to opt-out if running in paid plan.
11731
				return;
11732
			}
11733
11734
			if ( $this->add_ajax_action( 'stop_tracking', array( &$this, '_stop_tracking_callback' ) ) ) {
11735
				return;
11736
			}
11737
11738
			if ( $this->add_ajax_action( 'allow_tracking', array( &$this, '_allow_tracking_callback' ) ) ) {
11739
				return;
11740
			}
11741
11742
			if ( fs_request_is_action_secure( $this->_slug . '_reconnect' ) ) {
11743
				if ( ! $this->is_registered() && $this->is_anonymous() ) {
11744
					$this->connect_again();
11745
11746
					return;
11747
				}
11748
			}
11749
11750
			$url = '#';
11751
11752
			if ( $this->is_registered() ) {
11753
				if ( $this->is_tracking_allowed() ) {
11754
					$link_text_id = 'opt-out';
11755
				} else {
11756
					$link_text_id = 'opt-in';
11757
				}
11758
11759
				add_action( 'admin_footer', array( &$this, '_add_optout_dialog' ) );
11760
			} else {
11761
				$link_text_id = 'opt-in';
11762
11763
				$params = ! $this->is_anonymous() ?
11764
					array() :
11765
					array(
11766
						'nonce'     => wp_create_nonce( $this->_slug . '_reconnect' ),
11767
						'fs_action' => ( $this->_slug . '_reconnect' ),
11768
					);
11769
11770
				$url = $this->get_activation_url( $params );
11771
			}
11772
11773
			$this->add_plugin_action_link(
11774
				$this->get_text( $link_text_id ),
11775
				$url,
11776
				false,
11777
				13,
11778
				"opt-in-or-opt-out {$this->_slug}"
11779
			);
11780
		}
11781
11782
		/**
11783
		 * Get the URL of the page that should be loaded right after the plugin activation.
11784
		 *
11785
		 * @author Vova Feldman (@svovaf)
11786
		 * @since  1.1.7.4
11787
		 *
11788
		 * @return string
11789
		 */
11790
		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...
11791
			$url       = false;
11792
			$plugin_fs = false;
11793
11794
			if ( ! $this->is_addon() ) {
11795
				$first_time_path = $this->_menu->get_first_time_path();
11796
				$plugin_fs       = $this;
11797
				$url             = $plugin_fs->is_activation_mode() ?
11798
					$plugin_fs->get_activation_url() :
11799
					( empty( $first_time_path ) ?
11800
						$this->_get_admin_page_url() :
11801
						$first_time_path );
11802
			} else {
11803
				if ( $this->is_parent_plugin_installed() ) {
11804
					$plugin_fs = self::get_parent_instance();
11805
				}
11806
11807
				if ( is_object( $plugin_fs ) ) {
11808
					if ( ! $plugin_fs->is_registered() ) {
11809
						// Forward to parent plugin connect when parent not registered.
11810
						$url = $plugin_fs->get_activation_url();
11811
					} else {
11812
						// Forward to account page.
11813
						$url = $plugin_fs->_get_admin_page_url( 'account' );
11814
					}
11815
				}
11816
			}
11817
11818
			return $url;
11819
		}
11820
11821
		/**
11822
		 * Forward page to activation page.
11823
		 *
11824
		 * @author Vova Feldman (@svovaf)
11825
		 * @since  1.0.3
11826
		 */
11827
		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...
11828
			$url = $this->get_after_plugin_activation_redirect_url();
11829
11830
			if ( is_string( $url ) ) {
11831
				fs_redirect( $url );
11832
			}
11833
		}
11834
11835
		/**
11836
		 * Modify plugin's page action links collection.
11837
		 *
11838
		 * @author Vova Feldman (@svovaf)
11839
		 * @since  1.0.0
11840
		 *
11841
		 * @param array $links
11842
		 * @param       $file
11843
		 *
11844
		 * @return array
11845
		 */
11846
		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...
11847
			$this->_logger->entrance();
11848
11849
			$passed_deactivate = false;
11850
			$deactivate_link   = '';
11851
			$before_deactivate = array();
11852
			$after_deactivate  = array();
11853
			foreach ( $links as $key => $link ) {
11854
				if ( 'deactivate' === $key ) {
11855
					$deactivate_link   = $link;
11856
					$passed_deactivate = true;
11857
					continue;
11858
				}
11859
11860
				if ( ! $passed_deactivate ) {
11861
					$before_deactivate[ $key ] = $link;
11862
				} else {
11863
					$after_deactivate[ $key ] = $link;
11864
				}
11865
			}
11866
11867
			ksort( $this->_action_links );
11868
11869
			foreach ( $this->_action_links as $new_links ) {
11870
				foreach ( $new_links as $link ) {
11871
					$before_deactivate[ $link['key'] ] = '<a href="' . $link['href'] . '"' . ( $link['external'] ? ' target="_blank"' : '' ) . '>' . $link['label'] . '</a>';
11872
				}
11873
			}
11874
11875
			if ( ! empty( $deactivate_link ) ) {
11876
				/**
11877
				 * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link.
11878
				 *
11879
				 * @since 1.2.1.6 Always show the deactivation feedback form since we added automatic free version deactivation upon premium code activation.
11880
				 */
11881
				$deactivate_link .= '<i class="fs-slug" data-slug="' . $this->_slug . '"></i>';
11882
11883
				// Append deactivation link.
11884
				$before_deactivate['deactivate'] = $deactivate_link;
11885
			}
11886
11887
			return array_merge( $before_deactivate, $after_deactivate );
11888
		}
11889
11890
		/**
11891
		 * Adds admin message.
11892
		 *
11893
		 * @author Vova Feldman (@svovaf)
11894
		 * @since  1.0.4
11895
		 *
11896
		 * @param string $message
11897
		 * @param string $title
11898
		 * @param string $type
11899
		 */
11900
		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...
11901
			$this->_admin_notices->add( $message, $title, $type );
11902
		}
11903
11904
		/**
11905
		 * Adds sticky admin message.
11906
		 *
11907
		 * @author Vova Feldman (@svovaf)
11908
		 * @since  1.1.0
11909
		 *
11910
		 * @param string $message
11911
		 * @param string $id
11912
		 * @param string $title
11913
		 * @param string $type
11914
		 */
11915
		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...
11916
			$this->_admin_notices->add_sticky( $message, $id, $title, $type );
11917
		}
11918
11919
		/**
11920
		 * Helper function that returns the final steps for the upgrade completion.
11921
		 *
11922
		 * If the module is already running the premium code, returns an empty string.
11923
		 *
11924
		 * @author Vova Feldman (@svovaf)
11925
		 * @since  1.2.1
11926
		 *
11927
		 * @param string $plan_title
11928
		 *
11929
		 * @return string
11930
		 */
11931
		private function get_complete_upgrade_instructions( $plan_title = '' ) {
11932
			if ( ! $this->has_premium_version() || $this->is_premium() ) {
11933
				return '';
11934
			}
11935
11936
			if ( empty( $plan_title ) ) {
11937
				$plan_title = $this->_site->plan->title;
11938
			}
11939
11940
			// @since 1.2.1.5 The free version is auto deactivated.
11941
			$deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ?
11942
				( '<li>' . $this->get_text( 'deactivate-free-version' ) . '.</li>' ) :
11943
				'';
11944
11945
			return sprintf(
11946
				' %s: <ol><li>%s.</li>%s<li>%s (<a href="%s" target="_blank">%s</a>).</li></ol>',
11947
				$this->get_text( 'follow-steps-to-complete-upgrade' ),
11948
				$this->get_latest_download_link( sprintf(
11949
					$this->get_text( 'download-latest-x-version' ),
11950
					$plan_title
11951
				) ),
11952
				$deactivation_step,
11953
				$this->get_text( 'upload-and-activate' ),
11954
				'//bit.ly/upload-wp-plugin',
11955
				$this->get_text( 'howto-upload-activate' )
11956
			);
11957
		}
11958
11959
		/* Plugin Auto-Updates (@since 1.0.4)
11960
		------------------------------------------------------------------------------------------------------------------*/
11961
		/**
11962
		 * @var string[]
11963
		 */
11964
		private static $_auto_updated_plugins;
11965
11966
		/**
11967
		 * @todo   TEST IF IT WORKS!!!
11968
		 *
11969
		 * Include plugins for automatic updates based on stored settings.
11970
		 *
11971
		 * @see    http://wordpress.stackexchange.com/questions/131394/how-do-i-exclude-plugins-from-getting-automatically-updated/131404#131404
11972
		 *
11973
		 * @author Vova Feldman (@svovaf)
11974
		 * @since  1.0.4
11975
		 *
11976
		 * @param bool   $update Whether to update (not used for plugins)
11977
		 * @param object $item   The plugin's info
11978
		 *
11979
		 * @return bool
11980
		 */
11981
		static function _include_plugins_in_auto_update( $update, $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...
11982
			// Before version 3.8.2 the $item was the file name of the plugin,
11983
			// while in 3.8.2 statistics were added (https://core.trac.wordpress.org/changeset/27905).
11984
			$by_slug = ( (int) str_replace( '.', '', get_bloginfo( 'version' ) ) >= 382 );
11985
11986
			if ( ! isset( self::$_auto_updated_plugins ) ) {
11987
				$plugins = self::$_accounts->get_option( 'plugins', array() );
11988
11989
				$identifiers = array();
11990
				foreach ( $plugins as $p ) {
11991
					/**
11992
					 * @var FS_Plugin $p
11993
					 */
11994
					if ( isset( $p->auto_update ) && $p->auto_update ) {
11995
						$identifiers[] = ( $by_slug ? $p->slug : plugin_basename( $p->file ) );
11996
					}
11997
				}
11998
11999
				self::$_auto_updated_plugins = $identifiers;
12000
			}
12001
12002
			if ( in_array( $by_slug ? $item->slug : $item, self::$_auto_updated_plugins ) ) {
12003
				return true;
12004
			}
12005
12006
			// Pass update decision to next filters
12007
			return $update;
12008
		}
12009
12010
		/**
12011
		 * @author Vova Feldman (@svovaf)
12012
		 * @since  1.2.1.7
12013
		 *
12014
		 * @param string $key
12015
		 *
12016
		 * @return string
12017
		 */
12018
		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...
12019
			return fs_text( $key, $this->_slug );
12020
		}
12021
12022
		#----------------------------------------------------------------------------------
12023
		#region Versioning
12024
		#----------------------------------------------------------------------------------
12025
12026
		/**
12027
		 * Check if Freemius in SDK upgrade mode.
12028
		 *
12029
		 * @author Vova Feldman (@svovaf)
12030
		 * @since  1.0.9
12031
		 *
12032
		 * @return bool
12033
		 */
12034
		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...
12035
			return isset( $this->_storage->sdk_upgrade_mode ) ?
12036
				$this->_storage->sdk_upgrade_mode :
12037
				false;
12038
		}
12039
12040
		/**
12041
		 * Turn SDK upgrade mode off.
12042
		 *
12043
		 * @author Vova Feldman (@svovaf)
12044
		 * @since  1.0.9
12045
		 */
12046
		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...
12047
			$this->_storage->sdk_upgrade_mode = false;
12048
		}
12049
12050
		/**
12051
		 * Check if plugin upgrade mode.
12052
		 *
12053
		 * @author Vova Feldman (@svovaf)
12054
		 * @since  1.0.9
12055
		 *
12056
		 * @return bool
12057
		 */
12058
		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...
12059
			return isset( $this->_storage->plugin_upgrade_mode ) ?
12060
				$this->_storage->plugin_upgrade_mode :
12061
				false;
12062
		}
12063
12064
		/**
12065
		 * Turn plugin upgrade mode off.
12066
		 *
12067
		 * @author Vova Feldman (@svovaf)
12068
		 * @since  1.0.9
12069
		 */
12070
		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...
12071
			$this->_storage->plugin_upgrade_mode = false;
12072
		}
12073
12074
		#endregion
12075
12076
		#----------------------------------------------------------------------------------
12077
		#region Permissions
12078
		#----------------------------------------------------------------------------------
12079
12080
		/**
12081
		 * Check if specific permission requested.
12082
		 *
12083
		 * @author Vova Feldman (@svovaf)
12084
		 * @since  1.1.6
12085
		 *
12086
		 * @param string $permission
12087
		 *
12088
		 * @return bool
12089
		 */
12090
		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...
12091
			return isset( $this->_permissions[ $permission ] ) && ( true === $this->_permissions[ $permission ] );
12092
		}
12093
12094
		#endregion
12095
12096
		#----------------------------------------------------------------------------------
12097
		#region Auto Activation
12098
		#----------------------------------------------------------------------------------
12099
12100
		/**
12101
		 * Hints the SDK if running an auto-installation.
12102
		 *
12103
		 * @var bool
12104
		 */
12105
		private $_isAutoInstall = false;
12106
12107
		/**
12108
		 * After upgrade callback to install and auto activate a plugin.
12109
		 * This code will only be executed on explicit request from the user,
12110
		 * following the practice Jetpack are using with their theme installations.
12111
		 *
12112
		 * @link   https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
12113
		 *
12114
		 * @author Vova Feldman (@svovaf)
12115
		 * @since  1.2.1.7
12116
		 */
12117
		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...
12118
			$this->_logger->entrance();
12119
12120
			$this->check_ajax_referer( 'install_premium_version' );
12121
12122
			if ( ! $this->is_registered() ) {
12123
				// Not registered.
12124
				self::shoot_ajax_failure( array(
12125
					'message' => $this->get_text( 'auto-install-error-not-opted-in' ),
12126
					'code'    => 'premium_installed',
12127
				) );
12128
			}
12129
12130
			$plugin_id = fs_request_get( 'module_id', $this->get_id() );
12131
12132
			if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) {
12133
				// Invalid ID.
12134
				self::shoot_ajax_failure( array(
12135
					'message' => $this->get_text( 'auto-install-error-invalid-id' ),
12136
					'code'    => 'invalid_module_id',
12137
				) );
12138
			}
12139
12140
			if ( $plugin_id == $this->get_id() ) {
12141
				if ( $this->is_premium() ) {
12142
					// Already using the premium code version.
12143
					self::shoot_ajax_failure( array(
12144
						'message' => $this->get_text( 'auto-install-error-premium-activated' ),
12145
						'code'    => 'premium_installed',
12146
					) );
12147
				}
12148
				if ( ! $this->can_use_premium_code() ) {
12149
					// Don't have access to the premium code.
12150
					self::shoot_ajax_failure( array(
12151
						'message' => $this->get_text( 'auto-install-error-invalid-license' ),
12152
						'code'    => 'invalid_license',
12153
					) );
12154
				}
12155
				if ( ! $this->has_release_on_freemius() ) {
12156
					// Plugin is a serviceware, no premium code version.
12157
					self::shoot_ajax_failure( array(
12158
						'message' => $this->get_text( 'auto-install-error-serviceware' ),
12159
						'code'    => 'premium_version_missing',
12160
					) );
12161
				}
12162
			} else {
12163
				$addon = $this->get_addon( $plugin_id );
12164
12165
				if ( ! is_object( $addon ) ) {
12166
					// Invalid add-on ID.
12167
					self::shoot_ajax_failure( array(
12168
						'message' => $this->get_text( 'auto-install-error-invalid-id' ),
12169
						'code'    => 'invalid_module_id',
12170
					) );
12171
				}
12172
12173
				if ( $this->is_addon_activated( $plugin_id, true ) ) {
12174
					// Premium add-on version is already activated.
12175
					self::shoot_ajax_failure( array(
12176
						'message' => $this->get_text( 'auto-install-error-premium-addon-activated' ),
12177
						'code'    => 'premium_installed',
12178
					) );
12179
				}
12180
			}
12181
12182
			$this->_isAutoInstall = true;
12183
12184
			// Try to install and activate.
12185
			$updater = new FS_Plugin_Updater( $this );
12186
			$result  = $updater->install_and_activate_plugin( $plugin_id );
12187
12188
			if ( is_array( $result ) && ! empty( $result['message'] ) ) {
12189
				self::shoot_ajax_failure( array(
12190
					'message' => $result['message'],
12191
					'code'    => $result['code'],
12192
				) );
12193
			}
12194
12195
			self::shoot_ajax_success( $result );
12196
		}
12197
12198
		/**
12199
		 * Displays module activation dialog box after a successful upgrade
12200
		 * where the user explicitly requested to auto download and install
12201
		 * the premium version.
12202
		 *
12203
		 * @author Vova Feldman (@svovaf)
12204
		 * @since  1.2.1.7
12205
		 */
12206
		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...
12207
			$this->_logger->entrance();
12208
12209
			if ( ! $this->is_registered() ) {
12210
				// Not registered.
12211
				return;
12212
			}
12213
12214
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
12215
12216
			if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) {
12217
				// Invalid module ID.
12218
				return;
12219
			}
12220
12221
			if ( $plugin_id == $this->get_id() ) {
12222
				if ( $this->is_premium() ) {
12223
					// Already using the premium code version.
12224
					return;
12225
				}
12226
				if ( ! $this->can_use_premium_code() ) {
12227
					// Don't have access to the premium code.
12228
					return;
12229
				}
12230
				if ( ! $this->has_release_on_freemius() ) {
12231
					// Plugin is a serviceware, no premium code version.
12232
					return;
12233
				}
12234
			} else {
12235
				$addon = $this->get_addon( $plugin_id );
12236
12237
				if ( ! is_object( $addon ) ) {
12238
					// Invalid add-on ID.
12239
					return;
12240
				}
12241
12242
				if ( $this->is_addon_activated( $plugin_id, true ) ) {
12243
					// Premium add-on version is already activated.
12244
					return;
12245
				}
12246
			}
12247
12248
			$vars = array(
12249
				'id'   => $plugin_id,
12250
				'slug' => $this->_slug,
12251
			);
12252
12253
			fs_require_template( 'auto-installation.php', $vars );
12254
		}
12255
12256
		#endregion
12257
12258
		#----------------------------------------------------------------------------------
12259
		#region Marketing
12260
		#----------------------------------------------------------------------------------
12261
12262
		/**
12263
		 * Check if current user purchased any other plugins before.
12264
		 *
12265
		 * @author Vova Feldman (@svovaf)
12266
		 * @since  1.0.9
12267
		 *
12268
		 * @return bool
12269
		 */
12270
		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...
12271
			// TODO: Implement has_purchased_before() method.
12272
			throw new Exception( 'not implemented' );
12273
		}
12274
12275
		/**
12276
		 * Check if current user classified as an agency.
12277
		 *
12278
		 * @author Vova Feldman (@svovaf)
12279
		 * @since  1.0.9
12280
		 *
12281
		 * @return bool
12282
		 */
12283
		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...
12284
			// TODO: Implement is_agency() method.
12285
			throw new Exception( 'not implemented' );
12286
		}
12287
12288
		/**
12289
		 * Check if current user classified as a developer.
12290
		 *
12291
		 * @author Vova Feldman (@svovaf)
12292
		 * @since  1.0.9
12293
		 *
12294
		 * @return bool
12295
		 */
12296
		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...
12297
			// TODO: Implement is_developer() method.
12298
			throw new Exception( 'not implemented' );
12299
		}
12300
12301
		/**
12302
		 * Check if current user classified as a business.
12303
		 *
12304
		 * @author Vova Feldman (@svovaf)
12305
		 * @since  1.0.9
12306
		 *
12307
		 * @return bool
12308
		 */
12309
		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...
12310
			// TODO: Implement is_business() method.
12311
			throw new Exception( 'not implemented' );
12312
		}
12313
12314
		#endregion
12315
	}