GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — feature/gallery-template-clien... ( 80cd5b...be3a46 )
by Brad
02:36
created

Freemius   F

Complexity

Total Complexity 1687

Size/Duplication

Total Lines 13635
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 27

Importance

Changes 0
Metric Value
dl 0
loc 13635
rs 0.5217
c 0
b 0
f 0
wmc 1687
lcom 1
cbo 27

419 Methods

Rating   Name   Duplication   Size   Complexity  
A class-freemius.php ➔ fs_addons_body_class() 0 5 1
C __construct() 0 68 11
A has_settings_menu() 0 3 1
A is_free_wp_org_theme() 0 7 3
B is_submenu_item_visible() 0 25 4
A is_page_visible() 0 7 2
D _version_updates_handler() 0 44 10
B _data_migration() 0 30 6
A _after_version_update() 0 8 2
A _plugins_loaded() 0 19 3
D _register_hooks() 0 123 15
A unregister_uninstall_hook() 0 7 1
B clear_module_main_file_cache() 0 27 3
B _register_account_hooks() 0 22 6
C _find_caller_plugin_file() 0 50 8
B store_id_slug_type_path_map() 0 26 4
D get_caller_main_file_and_type() 0 92 16
B _add_deactivation_feedback_dialog_box() 0 46 5
C _get_uninstall_reasons() 0 156 8
B _submit_uninstall_reason_action() 0 41 4
C instance() 0 31 8
A has_instance() 0 3 1
B get_module_id() 0 13 5
A get_instance_by_id() 0 5 2
A get_instance_by_file() 0 7 2
A get_parent_instance() 0 3 1
A get_addon_instance() 0 5 1
C is_parent_plugin_installed() 0 31 7
A is_parent_in_activation() 0 8 2
B is_activation_mode() 0 8 8
A is_activation_page() 0 12 3
B is_matching_url() 0 28 6
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 29 3
A _load_textdomain() 0 14 2
B _add_debug_section() 0 36 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 41 6
A _debug_page_render() 0 16 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
C is_valid_email() 0 34 7
D _add_connectivity_issue_message() 0 219 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 109 6
A init() 0 10 1
F dynamic_init() 0 222 51
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 118 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
B _after_code_type_change() 0 25 6
B _plugin_code_type_changed() 0 60 4
A is_addon_activated() 0 18 3
B is_addon_connected() 0 27 6
A is_addon_installed() 0 7 1
B get_addon_basename() 0 28 6
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 55 11
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 5 2
D _admin_init_action() 0 105 22
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 3 1
A _delete_site_by_slug() 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
A get_previous_theme_slug() 0 5 2
A can_activate_previous_theme() 0 10 3
A activate_previous_theme() 0 15 2
A get_previous_theme_activation_url() 0 16 2
A _activate_theme_event_hook() 0 7 2
D _activate_plugin_event_hook() 0 106 19
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 13 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
B get_plugin_data() 0 33 3
A get_slug() 0 8 2
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_module_label() 0 13 4
A get_plugin_basename() 0 11 3
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_account_option() 0 7 2
A set_account_option() 0 8 1
A set_account_option_by_module() 0 7 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 9 2
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
B get_current_page() 0 21 5
A is_plugins_page() 0 3 1
A is_themes_page() 0 3 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 get_module_type() 0 8 2
A get_plugin_main_file_path() 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 31 9
A is_cron() 0 3 2
A is_user_in_admin() 0 3 3
A is_customizer() 0 3 1
A is_ssl_and_plan() 0 3 2
D _get_admin_page_url() 0 81 16
A is_admin_page() 0 3 1
A main_menu_url() 0 3 1
A is_theme_settings_page() 0 6 1
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 63 6
F opt_in() 0 153 27
F setup_account() 0 121 19
C _install_with_new_user() 0 29 7
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
B activate_parent_account() 0 46 4
A get_menu_items() 0 3 1
A get_menu_slug() 0 3 1
A _prepare_admin_menu() 0 14 3
C add_menu_action() 0 30 13
B _redirect_on_clicked_menu_link() 0 19 6
C override_plugin_menu_with_activation() 0 65 12
A get_top_level_menu_capability() 0 17 3
A get_top_level_menu_slug() 0 5 2
A get_pricing_cta_label() 0 14 4
A is_pricing_page_visible() 0 10 4
D add_submenu_items() 0 101 16
C embed_submenu_items() 0 53 10
B order_sub_submenu_items() 0 52 6
A get_support_forum_url() 0 3 1
A _add_default_submenu_items() 0 16 3
B add_submenu_item() 0 48 6
B add_submenu_link_item() 0 42 5
A get_action_tag() 0 3 1
A get_action_tag_static() 0 9 2
A get_unique_affix() 0 3 1
A get_module_unique_affix() 0 9 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 17 2
A _store_plans() 0 15 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 4 4
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 86 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 25 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 38 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 3
A get_reconnect_url() 0 6 1
A get_after_activation_url() 0 16 4
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
A is_in_trial_promotion() 0 3 1
D _add_trial_notice() 0 140 25
A _enqueue_common_css() 0 6 3
A _show_theme_activation_optin_dialog() 0 5 1
A _add_fs_theme_activation_dialog() 0 4 1
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 73 18
C get_after_plugin_activation_redirect_url() 0 30 8
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
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 _tabs_capture() 0 15 3
B _store_tabs_ajax_action() 0 24 4
C _store_tabs_styles() 0 34 7
A has_tabs() 0 3 1
A get_tabs_html() 0 5 1
C should_page_include_tabs() 0 30 8
B _add_tabs_before_content() 0 25 4
A _add_tabs_after_content() 0 11 2
A _add_freemius_tabs() 0 10 2
B _customizer_register() 0 31 4
A _style_premium_theme() 0 23 3
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     https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
	 * @since       1.0.3
7
	 */
8
	if ( ! defined( 'ABSPATH' ) ) {
9
		exit;
10
	}
11
12
	// "final class"
13
	class Freemius extends Freemius_Abstract {
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

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

namespace YourVendor;

class YourClass { }

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

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

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
313
			$this->_slug        = $this->get_slug();
314
			$this->_module_type = $this->get_module_type();
315
316
			$this->_storage = FS_Key_Value_Storage::instance( $this->_module_type . '_data', $this->_slug );
317
			$this->_cache   = FS_Cache_Manager::get_manager( WP_FS___OPTION_PREFIX . "cache_{$module_id}" );
318
319
			$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->get_unique_affix(), WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
320
321
			$this->_plugin_main_file_path = $this->_find_caller_plugin_file( $is_init );
322
			$this->_plugin_dir_path       = plugin_dir_path( $this->_plugin_main_file_path );
323
			$this->_plugin_basename       = $this->get_plugin_basename();
324
			$this->_free_plugin_basename  = str_replace( '-premium/', '/', $this->_plugin_basename );
325
326
			$base_name_split        = explode( '/', $this->_plugin_basename );
327
			$this->_plugin_dir_name = $base_name_split[0];
328
329
			if ( $this->_logger->is_on() ) {
330
				$this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path );
331
				$this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path );
332
				$this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename );
333
				$this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename );
334
				$this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name );
335
			}
336
337
			// Remember link between file to slug.
338
			$this->store_file_slug_map();
339
340
			// Store plugin's initial install timestamp.
341
			if ( ! isset( $this->_storage->install_timestamp ) ) {
342
				$this->_storage->install_timestamp = WP_FS__SCRIPT_START_TIME;
343
			}
344
345
			if ( ! is_object( $this->_plugin ) ) {
346
				$this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->get();
0 ignored issues
show
Documentation Bug introduced by
It seems like \FS_Plugin_Manager::inst...his->_module_id)->get() can also be of type false. However, the property $_plugin is declared as type object<FS_Plugin>. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
347
			}
348
349
			$this->_admin_notices = FS_Admin_Notice_Manager::instance(
350
				$this->_slug . ( $this->is_theme() ? ':theme' : '' ),
351
				/**
352
				 * Ensure that the admin notice will always have a title by using the stored plugin title if available and
353
				 * retrieving the title via the "get_plugin_name" method if there is no stored plugin title available.
354
				 *
355
				 * @author Leo Fajardo (@leorw)
356
				 * @since  1.2.2
357
				 */
358
				( is_object( $this->_plugin ) ? $this->_plugin->title : $this->get_plugin_name() ),
359
				$this->get_unique_affix()
360
			);
361
362
			if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) ||
363
			     'true' === fs_request_is_action( 'restart_freemius' )
364
			) {
365
				FS_Api::clear_cache();
366
				$this->_cache->clear();
367
			}
368
369
			$this->_register_hooks();
370
371
			$this->_load_account();
372
373
			$this->_version_updates_handler();
374
		}
375
376
		/**
377
		 * Checks whether this module has a settings menu.
378
		 *
379
		 * @author Leo Fajardo (@leorw)
380
		 * @since  1.2.2
381
		 *
382
		 * @return bool
383
		 */
384
		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...
385
			return $this->_menu->has_menu();
386
		}
387
388
		/**
389
		 * Check if the context module is free wp.org theme.
390
		 *
391
		 * This method is helpful because:
392
		 *      1. wp.org themes are limited to a single submenu item,
393
		 *         and sub-submenu items are most likely not allowed (never verified).
394
		 *      2. wp.org themes are not allowed to redirect the user
395
		 *         after the theme activation, therefore, the agreed UX
396
		 *         is showing the opt-in as a modal dialog box after
397
		 *         activation (approved by @otto42, @emiluzelac, @greenshady, @grapplerulrich).
398
		 *
399
		 * @author Vova Feldman (@svovaf)
400
		 * @since  1.2.2.7
401
		 *
402
		 * @return bool
403
		 */
404
		function is_free_wp_org_theme() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
405
			return (
406
				$this->is_theme() &&
407
				$this->is_org_repo_compliant() &&
408
				! $this->is_premium()
409
			);
410
		}
411
412
		/**
413
		 * Checks whether this a submenu item is visible.
414
		 *
415
		 * @author Vova Feldman (@svovaf)
416
		 * @since  1.2.2.6
417
		 * @since  1.2.2.7 Even if the menu item was specified to be hidden, when it is the context page, then show the submenu item so the user will have the right context page.
418
		 *
419
		 * @param string $slug
420
		 *
421
		 * @return bool
422
		 */
423
		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...
424
			if ( $this->is_admin_page( $slug ) ) {
425
				/**
426
				 * It is the current context page, so show the submenu item
427
				 * so the user will have the right context page, even if it
428
				 * was set to hidden.
429
				 */
430
				return true;
431
			}
432
433
			if ( ! $this->has_settings_menu() ) {
434
				// No menu settings at all.
435
				return false;
436
			}
437
438
			if ( $this->is_free_wp_org_theme() ) {
439
				/**
440
				 * wp.org themes are limited to a single submenu item, and
441
				 * sub-submenu items are most likely not allowed (never verified).
442
				 */
443
				return false;
444
			}
445
446
			return $this->_menu->is_submenu_item_visible( $slug );
447
		}
448
449
		/**
450
		 * Check if a Freemius page should be accessible via the UI.
451
		 *
452
		 * @author Vova Feldman (@svovaf)
453
		 * @since  1.2.2.7
454
		 *
455
		 * @param string $slug
456
		 *
457
		 * @return bool
458
		 */
459
		function is_page_visible( $slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
460
			if ( $this->is_admin_page( $slug ) ) {
461
				return true;
462
			}
463
464
			return $this->_menu->is_submenu_item_visible( $slug, true, true );
465
		}
466
467
		/**
468
		 * @author Vova Feldman (@svovaf)
469
		 * @since  1.0.9
470
		 */
471
		private function _version_updates_handler() {
472
			if ( ! isset( $this->_storage->sdk_version ) || $this->_storage->sdk_version != $this->version ) {
473
				// Freemius version upgrade mode.
474
				$this->_storage->sdk_last_version = $this->_storage->sdk_version;
475
				$this->_storage->sdk_version      = $this->version;
476
477
				if ( empty( $this->_storage->sdk_last_version ) ||
478
				     version_compare( $this->_storage->sdk_last_version, $this->version, '<' )
479
				) {
480
					$this->_storage->sdk_upgrade_mode   = true;
481
					$this->_storage->sdk_downgrade_mode = false;
482
				} else {
483
					$this->_storage->sdk_downgrade_mode = true;
484
					$this->_storage->sdk_upgrade_mode   = false;
485
486
				}
487
488
				$this->do_action( 'sdk_version_update', $this->_storage->sdk_last_version, $this->version );
489
			}
490
491
			$plugin_version = $this->get_plugin_version();
492
			if ( ! isset( $this->_storage->plugin_version ) || $this->_storage->plugin_version != $plugin_version ) {
493
				// Plugin version upgrade mode.
494
				$this->_storage->plugin_last_version = $this->_storage->plugin_version;
495
				$this->_storage->plugin_version      = $plugin_version;
496
497
				if ( empty( $this->_storage->plugin_last_version ) ||
498
				     version_compare( $this->_storage->plugin_last_version, $plugin_version, '<' )
499
				) {
500
					$this->_storage->plugin_upgrade_mode   = true;
501
					$this->_storage->plugin_downgrade_mode = false;
502
				} else {
503
					$this->_storage->plugin_downgrade_mode = true;
504
					$this->_storage->plugin_upgrade_mode   = false;
505
				}
506
507
				if ( ! empty( $this->_storage->plugin_last_version ) ) {
508
					// Different version of the plugin was installed before, therefore it's an update.
509
					$this->_storage->is_plugin_new_install = false;
510
				}
511
512
				$this->do_action( 'plugin_version_update', $this->_storage->plugin_last_version, $plugin_version );
513
			}
514
		}
515
516
		/**
517
		 * @author Vova Feldman (@svovaf)
518
		 * @since  1.1.5
519
		 *
520
		 * @param string $sdk_prev_version
521
		 * @param string $sdk_version
522
		 */
523
		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...
524
			/**
525
			 * @since 1.1.7.3 Fixed unwanted connectivity test cleanup.
526
			 */
527
			if ( empty( $sdk_prev_version ) ) {
528
				return;
529
			}
530
531
			if ( version_compare( $sdk_prev_version, '1.1.5', '<' ) &&
532
			     version_compare( $sdk_version, '1.1.5', '>=' )
533
			) {
534
				// On version 1.1.5 merged connectivity and is_on data.
535
				if ( isset( $this->_storage->connectivity_test ) ) {
536
					if ( ! isset( $this->_storage->is_on ) ) {
537
						unset( $this->_storage->connectivity_test );
538
					} else {
539
						$connectivity_data              = $this->_storage->connectivity_test;
540
						$connectivity_data['is_active'] = $this->_storage->is_on['is_active'];
541
						$connectivity_data['timestamp'] = $this->_storage->is_on['timestamp'];
542
543
						// Override.
544
						$this->_storage->connectivity_test = $connectivity_data;
545
546
						// Remove previous structure.
547
						unset( $this->_storage->is_on );
548
					}
549
550
				}
551
			}
552
		}
553
554
		/**
555
		 * @author Vova Feldman (@svovaf)
556
		 * @since  1.2.2.7
557
		 *
558
		 * @param string $plugin_prev_version
559
		 * @param string $plugin_version
560
		 */
561
		function _after_version_update( $plugin_prev_version, $plugin_version ) {
0 ignored issues
show
Unused Code introduced by
The parameter $plugin_prev_version is not used and could be removed.

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

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

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

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

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

Loading history...
562
			if ( $this->is_theme() ) {
563
				// Expire the cache of the previous tabs since the theme may
564
				// have setting updates.
565
				$this->_cache->expire( 'tabs' );
566
				$this->_cache->expire( 'tabs_stylesheets' );
567
			}
568
		}
569
570
		/**
571
		 * This action is connected to the 'plugins_loaded' hook and helps to determine
572
		 * if this is a new plugin installation or a plugin update.
573
		 *
574
		 * There are 3 different use-cases:
575
		 *    1) New plugin installation right with Freemius:
576
		 *       1.1 _activate_plugin_event_hook() will be executed first
577
		 *       1.2 Since $this->_storage->is_plugin_new_install is not set,
578
		 *           and $this->_storage->plugin_last_version is not set,
579
		 *           $this->_storage->is_plugin_new_install will be set to TRUE.
580
		 *       1.3 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
581
		 *           be already set to TRUE.
582
		 *
583
		 *    2) Plugin update, didn't have Freemius before, and now have the SDK:
584
		 *       2.1 _activate_plugin_event_hook() will not be executed, because
585
		 *           the activation hook do NOT fires on updates since WP 3.1.
586
		 *       2.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
587
		 *           be empty, therefore, it will be set to FALSE.
588
		 *
589
		 *    3) Plugin update, had Freemius in prev version as well:
590
		 *       3.1 _version_updates_handler() will be executed 1st, since FS was installed
591
		 *           before, $this->_storage->plugin_last_version will NOT be empty,
592
		 *           therefore, $this->_storage->is_plugin_new_install will be set to FALSE.
593
		 *       3.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install is
594
		 *           already set, therefore, it will not be modified.
595
		 *
596
		 *    Use-case #3 is backward compatible, #3.1 will be executed since 1.0.9.
597
		 *
598
		 * NOTE:
599
		 *    The only fallback of this mechanism is if an admin updates a plugin based on use-case #2,
600
		 *    and then, the next immediate PageView is the plugin's main settings page, it will not
601
		 *    show the opt-in right away. The reason it will happen is because Freemius execution
602
		 *    will be turned off till the plugin is fully loaded at least once
603
		 *    (till $this->_storage->was_plugin_loaded is TRUE).
604
		 *
605
		 * @author Vova Feldman (@svovaf)
606
		 * @since  1.1.9
607
		 *
608
		 */
609
		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...
610
			// Update flag that plugin was loaded with Freemius at least once.
611
			$this->_storage->was_plugin_loaded = true;
612
613
			/**
614
			 * Bug fix - only set to false when it's a plugin, due to the
615
			 * execution sequence of the theme hooks and our methods, if
616
			 * this will be set for themes, Freemius will always assume
617
			 * it's a theme update.
618
			 *
619
			 * @author Vova Feldman (@svovaf)
620
			 * @since  1.2.2.2
621
			 */
622
			if ( $this->is_plugin() &&
623
			     ! isset( $this->_storage->is_plugin_new_install )
624
			) {
625
				$this->_storage->is_plugin_new_install = false;
626
			}
627
		}
628
629
		/**
630
		 * @author Vova Feldman (@svovaf)
631
		 * @since  1.0.9
632
		 */
633
		private function _register_hooks() {
634
			$this->_logger->entrance();
635
636
			if ( is_admin() ) {
637
				if ( $this->is_plugin() ) {
638
					$plugin_dir = dirname( $this->_plugin_dir_path ) . '/';
639
640
					/**
641
					 * @since 1.2.2
642
					 *
643
					 * Hook to both free and premium version activations to support
644
					 * auto deactivation on the other version activation.
645
					 */
646
					register_activation_hook(
647
						$plugin_dir . $this->_free_plugin_basename,
648
						array( &$this, '_activate_plugin_event_hook' )
649
					);
650
651
					register_activation_hook(
652
						$plugin_dir . $this->premium_plugin_basename(),
653
						array( &$this, '_activate_plugin_event_hook' )
654
					);
655
				} else {
656
					add_action( 'after_switch_theme', array( &$this, '_activate_theme_event_hook' ), 10, 2 );
657
658
					/**
659
					 * Include the required hooks to capture the theme settings' page tabs
660
					 * and cache them.
661
					 *
662
					 * @author Vova Feldman (@svovaf)
663
					 * @since 1.2.2.7
664
					 */
665
					if ( ! $this->_cache->has_valid( 'tabs' ) ) {
666
						add_action( 'admin_footer', array( &$this, '_tabs_capture' ) );
667
						// Add license activation AJAX callback.
668
						$this->add_ajax_action( 'store_tabs', array( &$this, '_store_tabs_ajax_action' ) );
669
670
						add_action( 'admin_enqueue_scripts', array( &$this, '_store_tabs_styles' ), 9999999 );
671
					}
672
673
					add_action(
674
						'admin_footer',
675
						array( &$this, '_add_freemius_tabs' ),
676
						/**
677
						 * The tabs JS code must be executed after the tabs capture logic (_tabs_capture()).
678
						 * That's why the priority is 11 while the tabs capture logic is added
679
						 * with priority 10.
680
						 *
681
						 * @author Vova Feldman (@svovaf)
682
						 */
683
						11
684
					);
685
686
					add_action( 'admin_footer', array( &$this, '_style_premium_theme' ) );
687
				}
688
689
				/**
690
				 * Part of the mechanism to identify new plugin install vs. plugin update.
691
				 *
692
				 * @author Vova Feldman (@svovaf)
693
				 * @since  1.1.9
694
				 */
695
				if ( empty( $this->_storage->was_plugin_loaded ) ) {
696
					if ( $this->is_plugin() &&
697
					     $this->is_activation_mode( false ) &&
698
					     0 == did_action( 'plugins_loaded' )
699
					) {
700
						add_action( 'plugins_loaded', array( &$this, '_plugins_loaded' ) );
701
					} else {
702
						// If was activated before, then it was already loaded before.
703
						$this->_plugins_loaded();
704
					}
705
				}
706
707
				if ( ! self::is_ajax() ) {
708
					if ( ! $this->is_addon() ) {
709
						add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY );
710
					}
711
				}
712
			}
713
714
			if ( $this->is_plugin() ) {
715
				register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) );
716
			}
717
718
			if ( $this->is_theme() && self::is_customizer() ) {
719
				// Register customizer upsell.
720
				add_action( 'customize_register', array( &$this, '_customizer_register' ) );
721
			}
722
723
			add_action( 'init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY );
724
725
			add_action( 'admin_init', array( &$this, '_add_tracking_links' ) );
726
			add_action( 'admin_init', array( &$this, '_add_license_activation' ) );
727
			$this->add_ajax_action( 'update_billing', array( &$this, '_update_billing_ajax_action' ) );
728
			$this->add_ajax_action( 'start_trial', array( &$this, '_start_trial_ajax_action' ) );
729
730
			$this->add_ajax_action( 'install_premium_version', array(
731
				&$this,
732
				'_install_premium_version_ajax_action'
733
			) );
734
735
			$this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) );
736
737
			$this->add_action( 'sdk_version_update', array( &$this, '_data_migration' ), WP_FS__DEFAULT_PRIORITY, 2 );
738
			$this->add_action( 'plugin_version_update', array( &$this, '_after_version_update' ), WP_FS__DEFAULT_PRIORITY, 2 );
739
			$this->add_filter( 'after_code_type_change', array( &$this, '_after_code_type_change' ) );
740
741
			add_action( 'admin_init', array( &$this, '_add_trial_notice' ) );
742
			add_action( 'admin_init', array( &$this, '_enqueue_common_css' ) );
743
744
			/**
745
			 * Handle request to reset anonymous mode for `get_reconnect_url()`.
746
			 *
747
			 * @author Vova Feldman (@svovaf)
748
			 * @since  1.2.1.5
749
			 */
750
			if ( fs_request_is_action( 'reset_anonymous_mode' ) &&
751
			     $this->get_unique_affix() === fs_request_get( 'fs_unique_affix' )
752
			) {
753
				add_action( 'admin_init', array( &$this, 'connect_again' ) );
754
			}
755
		}
756
757
		/**
758
		 * Keeping the uninstall hook registered for free or premium plugin version may result to a fatal error that
759
		 * could happen when a user tries to uninstall either version while one of them is still active. Uninstalling a
760
		 * plugin will trigger inclusion of the free or premium version and if one of them is active during the
761
		 * uninstallation, a fatal error may occur in case the plugin's class or functions are already defined.
762
		 *
763
		 * @author Leo Fajardo (leorw)
764
		 *
765
		 * @since  1.2.0
766
		 */
767
		private function unregister_uninstall_hook() {
768
			$uninstallable_plugins = (array) get_option( 'uninstall_plugins' );
769
			unset( $uninstallable_plugins[ $this->_free_plugin_basename ] );
770
			unset( $uninstallable_plugins[ $this->premium_plugin_basename() ] );
771
772
			update_option( 'uninstall_plugins', $uninstallable_plugins );
773
		}
774
775
		/**
776
		 * @since 1.2.0 Invalidate module's main file cache, otherwise, FS_Plugin_Updater will not fetch updates.
777
		 */
778
		private function clear_module_main_file_cache() {
779
			if ( ! isset( $this->_storage->plugin_main_file ) ||
780
			     empty( $this->_storage->plugin_main_file->path )
781
			) {
782
				return;
783
			}
784
785
			$plugin_main_file = clone $this->_storage->plugin_main_file;
786
787
			// Store cached path (2nd layer cache).
788
			$plugin_main_file->prev_path = $plugin_main_file->path;
789
790
			// Clear cached path.
791
			unset( $plugin_main_file->path );
792
793
			$this->_storage->plugin_main_file = $plugin_main_file;
794
795
			/**
796
			 * Clear global cached path.
797
			 *
798
			 * @author Leo Fajardo (@leorw)
799
			 * @since  1.2.2
800
			 */
801
			$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map' );
802
			unset( $id_slug_type_path_map[ $this->_module_id ]['path'] );
803
			self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true );
804
		}
805
806
		/**
807
		 * @author Vova Feldman (@svovaf)
808
		 * @since  1.0.9
809
		 */
810
		private function _register_account_hooks() {
811
			if ( ! is_admin() ) {
812
				return;
813
			}
814
815
			/**
816
			 * Always show the deactivation feedback form since we added
817
			 * automatic free version deactivation upon premium code activation.
818
			 *
819
			 * @since 1.2.1.6
820
			 */
821
			$this->add_ajax_action(
822
				'submit_uninstall_reason',
823
				array( &$this, '_submit_uninstall_reason_action' )
824
			);
825
826
			if ( ( $this->is_plugin() && self::is_plugins_page() ) ||
827
			     ( $this->is_theme() && self::is_themes_page() )
828
			) {
829
				add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) );
830
			}
831
		}
832
833
		/**
834
		 * Leverage backtrace to find caller plugin file path.
835
		 *
836
		 * @author Vova Feldman (@svovaf)
837
		 * @since  1.0.6
838
		 *
839
		 * @param  bool $is_init Is initiation sequence.
840
		 *
841
		 * @return string
842
		 */
843
		private function _find_caller_plugin_file( $is_init = false ) {
844
			// Try to load the cached value of the file path.
845
			if ( isset( $this->_storage->plugin_main_file ) ) {
846
				$plugin_main_file = $this->_storage->plugin_main_file;
847
				if ( isset( $plugin_main_file->path ) && file_exists( $plugin_main_file->path ) ) {
848
					return $plugin_main_file->path;
849
				}
850
			}
851
852
			/**
853
			 * @since 1.2.1
854
			 *
855
			 * `clear_module_main_file_cache()` is clearing the plugin's cached path on
856
			 * deactivation. Therefore, if any plugin/theme was initiating `Freemius`
857
			 * with that plugin's slug, it was overriding the empty plugin path with a wrong path.
858
			 *
859
			 * So, we've added a special mechanism with a 2nd layer of cache that uses `prev_path`
860
			 * when the class instantiator isn't the module.
861
			 */
862
			if ( ! $is_init ) {
863
				// Fetch prev path cache.
864
				if ( isset( $this->_storage->plugin_main_file ) &&
865
				     isset( $this->_storage->plugin_main_file->prev_path )
866
				) {
867
					if ( file_exists( $this->_storage->plugin_main_file->prev_path ) ) {
868
						return $this->_storage->plugin_main_file->prev_path;
869
					}
870
				}
871
872
				wp_die(
873
					$this->get_text( 'failed-finding-main-path' ) .
874
					" Module: {$this->_slug}; SDK: " . WP_FS__SDK_VERSION . ";",
875
					$this->get_text( 'error' ),
876
					array( 'back_link' => true )
877
				);
878
			}
879
880
			/**
881
			 * @since 1.2.1
882
			 *
883
			 * Only the original instantiator that calls dynamic_init can modify the module's path.
884
			 */
885
			// Find caller module.
886
			$id_slug_type_path_map            = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
887
			$this->_storage->plugin_main_file = (object) array(
888
				'path' => $id_slug_type_path_map[ $this->_module_id ]['path'],
889
			);
890
891
			return $id_slug_type_path_map[ $this->_module_id ]['path'];
892
		}
893
894
		/**
895
		 * @author Leo Fajardo (@leorw)
896
		 *
897
		 * @param number $module_id
898
		 * @param string $slug
899
		 *
900
		 * @since  1.2.2
901
		 */
902
		private function store_id_slug_type_path_map( $module_id, $slug ) {
903
			$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
904
905
			$store_option = false;
906
907
			if ( ! isset( $id_slug_type_path_map[ $module_id ] ) ) {
908
				$id_slug_type_path_map[ $module_id ] = array(
909
					'slug' => $slug
910
				);
911
912
				$store_option = true;
913
			}
914
915
			if ( ! isset( $id_slug_type_path_map[ $module_id ]['path'] ) ) {
916
				$caller_main_file_and_type = $this->get_caller_main_file_and_type();
917
918
				$id_slug_type_path_map[ $module_id ]['type'] = $caller_main_file_and_type->module_type;
919
				$id_slug_type_path_map[ $module_id ]['path'] = $caller_main_file_and_type->path;
920
921
				$store_option = true;
922
			}
923
924
			if ( $store_option ) {
925
				self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true );
926
			}
927
		}
928
929
		/**
930
		 * Identifies the caller type: plugin or theme.
931
		 *
932
		 * @author Leo Fajardo (@leorw)
933
		 * @since  1.2.2
934
		 *
935
		 * @author Vova Feldman (@svovaf)
936
		 * @since  1.2.2.3 Find the earliest module in the call stack that calls to the SDK. This fix is for cases when
937
		 *         add-ons are relying on loading the SDK from the parent module, and also allows themes including the
938
		 *         SDK an internal file instead of directly from functions.php.
939
		 * @since  1.2.1.7 Knows how to handle cases when an add-on includes the parent module logic.
940
		 */
941
		private function get_caller_main_file_and_type() {
942
			self::require_plugin_essentials();
943
944
			$all_plugins       = get_plugins();
945
			$all_plugins_paths = array();
946
947
			// Get active plugin's main files real full names (might be symlinks).
948
			foreach ( $all_plugins as $relative_path => &$data ) {
949
				if ( false === strpos( fs_normalize_path( $relative_path ), '/' ) ) {
950
					/**
951
					 * Ignore plugins that don't have a folder (e.g. Hello Dolly) since they
952
					 * can't really include the SDK.
953
					 *
954
					 * @author Vova Feldman
955
					 * @since  1.2.1.7
956
					 */
957
					continue;
958
				}
959
960
				$all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) );
961
			}
962
963
			$caller_file_candidate = false;
964
			$caller_map            = array();
965
			$module_type           = WP_FS__MODULE_TYPE_PLUGIN;
966
			$themes_dir            = fs_normalize_path( get_theme_root() );
967
968
			for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) {
969
				if ( empty( $bt[ $i ]['file'] ) ) {
970
					continue;
971
				}
972
973
				if ( $i > 1 && ! empty( $bt[ $i - 1 ]['file'] ) && $bt[ $i ]['file'] === $bt[ $i - 1 ]['file'] ) {
974
					// If file same as the prev file in the stack, skip it.
975
					continue;
976
				}
977
978
				if ( ! empty( $bt[ $i ]['function'] ) && in_array( $bt[ $i ]['function'], array(
979
						'do_action',
980
						'apply_filter',
981
						// The string split is stupid, but otherwise, theme check
982
						// throws info notices.
983
						'requir' . 'e_once',
984
						'requir' . 'e',
985
						'includ' . 'e_once',
986
						'includ' . 'e'
987
					) )
988
				) {
989
					// Ignore call stack hooks and files inclusion.
990
					continue;
991
				}
992
993
				$caller_file_path = fs_normalize_path( $bt[ $i ]['file'] );
994
995
				if ( 'functions.php' === basename( $caller_file_path ) ) {
996
					/**
997
					 * 1. Assumes that theme's starting execution file is functions.php.
998
					 * 2. This complex logic fixes symlink issues (e.g. with Vargant).
999
					 *
1000
					 * @author Vova Feldman (@svovaf)
1001
					 * @since  1.2.2.5
1002
					 */
1003
1004
					if ( $caller_file_path == fs_normalize_path( realpath( trailingslashit( $themes_dir ) . basename( dirname( $caller_file_path ) ) . '/' . basename( $caller_file_path ) ) ) ) {
1005
						$module_type           = WP_FS__MODULE_TYPE_THEME;
1006
						$caller_file_candidate = $caller_file_path;
1007
						continue;
1008
					}
1009
				}
1010
1011
				$caller_file_hash = md5( $caller_file_path );
1012
1013
				if ( ! isset( $caller_map[ $caller_file_hash ] ) ) {
1014
					foreach ( $all_plugins_paths as $plugin_path ) {
1015
						if ( false !== strpos( $caller_file_path, fs_normalize_path( dirname( $plugin_path ) . '/' ) ) ) {
1016
							$caller_map[ $caller_file_hash ] = fs_normalize_path( $plugin_path );
1017
							break;
1018
						}
1019
					}
1020
				}
1021
1022
				if ( isset( $caller_map[ $caller_file_hash ] ) ) {
1023
					$module_type           = WP_FS__MODULE_TYPE_PLUGIN;
1024
					$caller_file_candidate = $caller_map[ $caller_file_hash ];
1025
				}
1026
			}
1027
1028
			return (object) array(
1029
				'module_type' => $module_type,
1030
				'path'        => $caller_file_candidate
1031
			);
1032
		}
1033
1034
		#----------------------------------------------------------------------------------
1035
		#region Deactivation Feedback Form
1036
		#----------------------------------------------------------------------------------
1037
1038
		/**
1039
		 * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins
1040
		 * page.
1041
		 *
1042
		 * @author Vova Feldman (@svovaf)
1043
		 * @author Leo Fajardo (@leorw)
1044
		 * @since  1.1.2
1045
		 */
1046
		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...
1047
			/* Check the type of user:
1048
			 * 1. Long-term (long-term)
1049
			 * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term).
1050
			 * 3. Short-term (short-term)
1051
			 */
1052
			$is_long_term_user = true;
1053
1054
			// Check if the site is at least 2 days old.
1055
			$time_installed = $this->_storage->install_timestamp;
1056
1057
			// Difference in seconds.
1058
			$date_diff = time() - $time_installed;
1059
1060
			// Convert seconds to days.
1061
			$date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) );
1062
1063
			if ( $date_diff_days < 2 ) {
1064
				$is_long_term_user = false;
1065
			}
1066
1067
			$is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user );
1068
1069
			if ( $is_long_term_user ) {
1070
				$user_type = 'long-term';
1071
			} else {
1072
				if ( ! $this->is_registered() && ! $this->is_anonymous() ) {
1073
					$user_type = 'non-registered-and-non-anonymous-short-term';
1074
				} else {
1075
					$user_type = 'short-term';
1076
				}
1077
			}
1078
1079
			$uninstall_reasons = $this->_get_uninstall_reasons( $user_type );
1080
1081
			// Load the HTML template for the deactivation feedback dialog box.
1082
			$vars = array(
1083
				'reasons' => $uninstall_reasons,
1084
				'id'      => $this->_module_id
1085
			);
1086
1087
			/**
1088
			 * @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.
1089
			 */
1090
			fs_require_template( 'forms/deactivation/form.php', $vars );
1091
		}
1092
1093
		/**
1094
		 * @author Leo Fajardo (leorw)
1095
		 * @since  1.1.2
1096
		 *
1097
		 * @param string $user_type
1098
		 *
1099
		 * @return array The uninstall reasons for the specified user type.
1100
		 */
1101
		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...
1102
			$module_type = $this->_module_type;
1103
1104
			$internal_message_template_var = array(
1105
				'id' => $this->_module_id
1106
			);
1107
1108
			if ( $this->is_registered() && false !== $this->get_plan() && $this->get_plan()->has_technical_support() ) {
1109
				$contact_support_template = fs_get_template( 'forms/deactivation/contact.php', $internal_message_template_var );
1110
			} else {
1111
				$contact_support_template = '';
1112
			}
1113
1114
			$reason_found_better_plugin = array(
1115
				'id'                => self::REASON_FOUND_A_BETTER_PLUGIN,
1116
				'text'              => sprintf( $this->get_text( 'reason-found-a-better-plugin' ), $module_type ),
1117
				'input_type'        => 'textfield',
1118
				'input_placeholder' => sprintf( $this->get_text( 'placeholder-plugin-name' ), $module_type ),
1119
			);
1120
1121
			$reason_temporary_deactivation = array(
1122
				'id'                => self::REASON_TEMPORARY_DEACTIVATION,
1123
				'text'              => sprintf(
1124
					$this->get_text( 'reason-temporary-x' ),
1125
					strtolower( $this->is_plugin() ?
1126
						$this->get_text( 'deactivation' ) :
1127
						$this->get_text( 'theme-switch' )
1128
					)
1129
				),
1130
				'input_type'        => '',
1131
				'input_placeholder' => ''
1132
			);
1133
1134
			$reason_other = array(
1135
				'id'                => self::REASON_OTHER,
1136
				'text'              => $this->get_text( 'reason-other' ),
1137
				'input_type'        => 'textfield',
1138
				'input_placeholder' => ''
1139
			);
1140
1141
			$long_term_user_reasons = array(
1142
				array(
1143
					'id'                => self::REASON_NO_LONGER_NEEDED,
1144
					'text'              => sprintf( $this->get_text( 'reason-no-longer-needed' ), $module_type ),
1145
					'input_type'        => '',
1146
					'input_placeholder' => ''
1147
				),
1148
				$reason_found_better_plugin,
1149
				array(
1150
					'id'                => self::REASON_NEEDED_FOR_A_SHORT_PERIOD,
1151
					'text'              => sprintf( $this->get_text( 'reason-needed-for-a-short-period' ), $module_type ),
1152
					'input_type'        => '',
1153
					'input_placeholder' => ''
1154
				),
1155
				array(
1156
					'id'                => self::REASON_BROKE_MY_SITE,
1157
					'text'              => sprintf( $this->get_text( 'reason-broke-my-site' ), $module_type ),
1158
					'input_type'        => '',
1159
					'input_placeholder' => '',
1160
					'internal_message'  => $contact_support_template
1161
				),
1162
				array(
1163
					'id'                => self::REASON_SUDDENLY_STOPPED_WORKING,
1164
					'text'              => sprintf( $this->get_text( 'reason-suddenly-stopped-working' ), $module_type ),
1165
					'input_type'        => '',
1166
					'input_placeholder' => '',
1167
					'internal_message'  => $contact_support_template
1168
				)
1169
			);
1170
1171
			if ( $this->is_paying() ) {
1172
				$long_term_user_reasons[] = array(
1173
					'id'                => self::REASON_CANT_PAY_ANYMORE,
1174
					'text'              => $this->get_text( 'reason-cant-pay-anymore' ),
1175
					'input_type'        => 'textfield',
1176
					'input_placeholder' => $this->get_text( 'placeholder-comfortable-price' )
1177
				);
1178
			}
1179
1180
			$reason_dont_share_info = array(
1181
				'id'                => self::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION,
1182
				'text'              => $this->get_text( 'reason-dont-like-to-share-my-information' ),
1183
				'input_type'        => '',
1184
				'input_placeholder' => ''
1185
			);
1186
1187
			/**
1188
			 * If the current user has selected the "don't share data" reason in the deactivation feedback modal, inform the
1189
			 * user by showing additional message that he doesn't have to share data and can just choose to skip the opt-in
1190
			 * (the Skip button is included in the message to show). This message will only be shown if anonymous mode is
1191
			 * enabled and the user's account is currently not in pending activation state (similar to the way the Skip
1192
			 * button in the opt-in form is shown/hidden).
1193
			 */
1194
			if ( $this->is_enable_anonymous() && ! $this->is_pending_activation() ) {
1195
				$reason_dont_share_info['internal_message'] = fs_get_template( 'forms/deactivation/retry-skip.php', $internal_message_template_var );
1196
			}
1197
1198
			$uninstall_reasons = array(
1199
				'long-term'                                   => $long_term_user_reasons,
1200
				'non-registered-and-non-anonymous-short-term' => array(
1201
					array(
1202
						'id'                => self::REASON_DIDNT_WORK,
1203
						'text'              => sprintf( $this->get_text( 'reason-didnt-work' ), $module_type ),
1204
						'input_type'        => '',
1205
						'input_placeholder' => ''
1206
					),
1207
					$reason_dont_share_info,
1208
					$reason_found_better_plugin
1209
				),
1210
				'short-term'                                  => array(
1211
					array(
1212
						'id'                => self::REASON_COULDNT_MAKE_IT_WORK,
1213
						'text'              => $this->get_text( 'reason-couldnt-make-it-work' ),
1214
						'input_type'        => '',
1215
						'input_placeholder' => '',
1216
						'internal_message'  => $contact_support_template
1217
					),
1218
					$reason_found_better_plugin,
1219
					array(
1220
						'id'                => self::REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE,
1221
						'text'              => sprintf( $this->get_text( 'reason-great-but-need-specific-feature' ), $module_type ),
1222
						'input_type'        => 'textarea',
1223
						'input_placeholder' => $this->get_text( 'placeholder-feature' )
1224
					),
1225
					array(
1226
						'id'                => self::REASON_NOT_WORKING,
1227
						'text'              => sprintf( $this->get_text( 'reason-not-working' ), $module_type ),
1228
						'input_type'        => 'textarea',
1229
						'input_placeholder' => $this->get_text( 'placeholder-share-what-didnt-work' )
1230
					),
1231
					array(
1232
						'id'                => self::REASON_NOT_WHAT_I_WAS_LOOKING_FOR,
1233
						'text'              => $this->get_text( 'reason-not-what-i-was-looking-for' ),
1234
						'input_type'        => 'textarea',
1235
						'input_placeholder' => $this->get_text( 'placeholder-what-youve-been-looking-for' )
1236
					),
1237
					array(
1238
						'id'                => self::REASON_DIDNT_WORK_AS_EXPECTED,
1239
						'text'              => sprintf( $this->get_text( 'reason-didnt-work-as-expected' ), $module_type ),
1240
						'input_type'        => 'textarea',
1241
						'input_placeholder' => $this->get_text( 'placeholder-what-did-you-expect' )
1242
					)
1243
				)
1244
			);
1245
1246
			// Randomize the reasons for the current user type.
1247
			shuffle( $uninstall_reasons[ $user_type ] );
1248
1249
			// Keep the following reasons as the last items in the list.
1250
			$uninstall_reasons[ $user_type ][] = $reason_temporary_deactivation;
1251
			$uninstall_reasons[ $user_type ][] = $reason_other;
1252
1253
			$uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons );
1254
1255
			return $uninstall_reasons[ $user_type ];
1256
		}
1257
1258
		/**
1259
		 * Called after the user has submitted his reason for deactivating the plugin.
1260
		 *
1261
		 * @author Leo Fajardo (@leorw)
1262
		 * @since  1.1.2
1263
		 */
1264
		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...
1265
			$this->_logger->entrance();
1266
1267
			$this->check_ajax_referer( 'submit_uninstall_reason' );
1268
1269
			$reason_id = fs_request_get( 'reason_id' );
1270
1271
			// Check if the given reason ID is an unsigned integer.
1272
			if ( ! ctype_digit( $reason_id ) ) {
1273
				exit;
1274
			}
1275
1276
			$reason_info = trim( fs_request_get( 'reason_info', '' ) );
1277
			if ( ! empty( $reason_info ) ) {
1278
				$reason_info = substr( $reason_info, 0, 128 );
1279
			}
1280
1281
			$reason = (object) array(
1282
				'id'           => $reason_id,
1283
				'info'         => $reason_info,
1284
				'is_anonymous' => fs_request_get_bool( 'is_anonymous' )
1285
			);
1286
1287
			$this->_storage->store( 'uninstall_reason', $reason );
1288
1289
			/**
1290
			 * If the module type is "theme", trigger the uninstall event here (on theme deactivation) since themes do
1291
			 * not support uninstall hook.
1292
			 *
1293
			 * @author Leo Fajardo (@leorw)
1294
			 * @since  1.2.2
1295
			 */
1296
			if ( $this->is_theme() ) {
1297
				$this->_uninstall_plugin_event( false );
1298
				$this->remove_sdk_reference();
1299
			}
1300
1301
			// Print '1' for successful operation.
1302
			echo 1;
1303
			exit;
1304
		}
1305
1306
		#endregion
1307
1308
		#----------------------------------------------------------------------------------
1309
		#region Instance
1310
		#----------------------------------------------------------------------------------
1311
1312
		/**
1313
		 * Main singleton instance.
1314
		 *
1315
		 * @author Vova Feldman (@svovaf)
1316
		 * @since  1.0.0
1317
		 *
1318
		 * @param  number      $module_id
1319
		 * @param  string|bool $slug
1320
		 * @param  bool        $is_init Is initiation sequence.
1321
		 *
1322
		 * @return Freemius|false
1323
		 */
1324
		static function instance( $module_id, $slug = false, $is_init = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1325
			if ( empty( $module_id ) ) {
1326
				return false;
1327
			}
1328
1329
			if ( ! is_numeric( $module_id ) ) {
1330
				if ( ! $is_init && true === $slug ) {
1331
					$is_init = true;
1332
				}
1333
1334
				$slug = $module_id;
1335
1336
				$module = FS_Plugin_Manager::instance( $slug )->get();
1337
1338
				if ( is_object( $module ) ) {
1339
					$module_id = $module->id;
1340
				}
1341
			}
1342
1343
			$key = 'm_' . $module_id;
1344
1345
			if ( ! isset( self::$_instances[ $key ] ) ) {
1346
				if ( 0 === count( self::$_instances ) ) {
1347
					self::_load_required_static();
1348
				}
1349
1350
				self::$_instances[ $key ] = new Freemius( $module_id, $slug, $is_init );
1351
			}
1352
1353
			return self::$_instances[ $key ];
1354
		}
1355
1356
		/**
1357
		 * @author Vova Feldman (@svovaf)
1358
		 * @since  1.0.6
1359
		 *
1360
		 * @param number $addon_id
1361
		 *
1362
		 * @return bool
1363
		 */
1364
		private static function has_instance( $addon_id ) {
1365
			return isset( self::$_instances[ 'm_' . $addon_id ] );
1366
		}
1367
1368
		/**
1369
		 * @author Leo Fajardo (@leorw)
1370
		 * @since  1.2.2
1371
		 *
1372
		 * @param  string|number $id_or_slug
1373
		 *
1374
		 * @return number|false
1375
		 */
1376
		private static function get_module_id( $id_or_slug ) {
1377
			if ( is_numeric( $id_or_slug ) ) {
1378
				return $id_or_slug;
1379
			}
1380
1381
			foreach ( self::$_instances as $instance ) {
1382
				if ( $instance->is_plugin() && ( $id_or_slug === $instance->get_slug() ) ) {
1383
					return $instance->get_id();
1384
				}
1385
			}
1386
1387
			return false;
1388
		}
1389
1390
		/**
1391
		 * @author Vova Feldman (@svovaf)
1392
		 * @since  1.0.6
1393
		 *
1394
		 * @param number $id
1395
		 *
1396
		 * @return false|Freemius
1397
		 */
1398
		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...
1399
			return isset ( self::$_instances[ 'm_' . $id ] ) ?
1400
				self::$_instances[ 'm_' . $id ] :
1401
				false;
1402
		}
1403
1404
		/**
1405
		 *
1406
		 * @author Vova Feldman (@svovaf)
1407
		 * @since  1.0.1
1408
		 *
1409
		 * @param $plugin_file
1410
		 *
1411
		 * @return false|Freemius
1412
		 */
1413
		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...
1414
			$slug = self::find_slug_by_basename( $plugin_file );
1415
1416
			return ( false !== $slug ) ?
1417
				self::instance( self::get_module_id( $slug ) ) :
1418
				false;
1419
		}
1420
1421
		/**
1422
		 * @author Vova Feldman (@svovaf)
1423
		 * @since  1.0.6
1424
		 *
1425
		 * @return false|Freemius
1426
		 */
1427
		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...
1428
			return self::get_instance_by_id( $this->_plugin->parent_plugin_id );
1429
		}
1430
1431
		/**
1432
		 * @author Vova Feldman (@svovaf)
1433
		 * @since  1.0.6
1434
		 *
1435
		 * @param  string|number $id_or_slug
1436
		 *
1437
		 * @return false|Freemius
1438
		 */
1439
		function get_addon_instance( $id_or_slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1440
			$addon_id = self::get_module_id( $id_or_slug );
1441
1442
			return self::instance( $addon_id );
1443
		}
1444
1445
		#endregion ------------------------------------------------------------------
1446
1447
		/**
1448
		 * @author Vova Feldman (@svovaf)
1449
		 * @since  1.0.6
1450
		 *
1451
		 * @return bool
1452
		 */
1453
		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...
1454
			$is_active = self::has_instance( $this->_plugin->parent_plugin_id );
1455
1456
			if ( $is_active ) {
1457
				return true;
1458
			}
1459
1460
			/**
1461
			 * Parent module might be a theme. If that's the case, the add-on's FS
1462
			 * instance will be loaded prior to the theme's FS instance, therefore,
1463
			 * we need to check if it's active with a "look ahead".
1464
			 *
1465
			 * @author Vova Feldman
1466
			 * @since  1.2.2.3
1467
			 */
1468
			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...
1469
			if ( is_object( $fs_active_plugins ) && is_array( $fs_active_plugins->plugins ) ) {
1470
				$active_theme = wp_get_theme();
1471
1472
				foreach ( $fs_active_plugins->plugins as $sdk => $module ) {
1473
					if ( WP_FS__MODULE_TYPE_THEME === $module->type ) {
1474
						if ( $module->plugin_path == $active_theme->get_stylesheet() ) {
1475
							// Parent module is a theme and it's currently active.
1476
							return true;
1477
						}
1478
					}
1479
				}
1480
			}
1481
1482
			return false;
1483
		}
1484
1485
		/**
1486
		 * Check if add-on parent plugin in activation mode.
1487
		 *
1488
		 * @author Vova Feldman (@svovaf)
1489
		 * @since  1.0.7
1490
		 *
1491
		 * @return bool
1492
		 */
1493
		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...
1494
			$parent_fs = $this->get_parent_instance();
1495
			if ( ! is_object( $parent_fs ) ) {
1496
				return false;
1497
			}
1498
1499
			return ( $parent_fs->is_activation_mode() );
1500
		}
1501
1502
		/**
1503
		 * Is plugin in activation mode.
1504
		 *
1505
		 * @author Vova Feldman (@svovaf)
1506
		 * @since  1.0.7
1507
		 *
1508
		 * @param bool $and_on
1509
		 *
1510
		 * @return bool
1511
		 */
1512
		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...
1513
			return (
1514
				( $this->is_on() || ! $and_on ) &&
1515
				( ! $this->is_registered() || ( $this->is_only_premium() && ! $this->has_features_enabled_license() ) ) &&
1516
				( ! $this->is_enable_anonymous() ||
1517
				  ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) )
1518
			);
1519
		}
1520
1521
		/**
1522
		 * Check if current page is the opt-in/pending-activation page.
1523
		 *
1524
		 * @author Vova Feldman (@svovaf)
1525
		 * @since  1.2.1.7
1526
		 *
1527
		 * @return bool
1528
		 */
1529
		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...
1530
			if ( $this->_menu->is_main_settings_page() ) {
1531
				return true;
1532
			}
1533
1534
			if ( ! $this->is_activation_mode() ) {
1535
				return false;
1536
			}
1537
1538
			// Check if current page is matching the activation page.
1539
			return $this->is_matching_url( $this->get_activation_url() );
1540
		}
1541
1542
		/**
1543
		 * Check if URL path's are matching and that all querystring
1544
		 * arguments of the $sub_url exist in the $url with the same values.
1545
		 *
1546
		 * WARNING:
1547
		 *  1. This method doesn't check if the sub/domain are matching.
1548
		 *  2. Ignore case sensitivity.
1549
		 *
1550
		 * @author Vova Feldman (@svovaf)
1551
		 * @since  1.2.1.7
1552
		 *
1553
		 * @param string $sub_url
1554
		 * @param string $url     If argument is not set, check if the sub_url matching the current's page URL.
1555
		 *
1556
		 * @return bool
1557
		 */
1558
		private function is_matching_url( $sub_url, $url = '' ) {
0 ignored issues
show
Coding Style introduced by
is_matching_url uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1559
			if ( empty( $url ) ) {
1560
				$url = $_SERVER['REQUEST_URI'];
1561
			}
1562
1563
			$url     = strtolower( $url );
1564
			$sub_url = strtolower( $sub_url );
1565
1566
			if ( parse_url( $sub_url, PHP_URL_PATH ) !== parse_url( $url, PHP_URL_PATH ) ) {
1567
				// Different path - DO NOT OVERRIDE PAGE.
1568
				return false;
1569
			}
1570
1571
			$url_params = array();
1572
			parse_str( parse_url( $url, PHP_URL_QUERY ), $url_params );
1573
1574
			$sub_url_params = array();
1575
			parse_str( parse_url( $sub_url, PHP_URL_QUERY ), $sub_url_params );
1576
1577
			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...
1578
				if ( ! isset( $url_params[ $key ] ) || $val != $url_params[ $key ] ) {
1579
					// Not matching query string - DO NOT OVERRIDE PAGE.
1580
					return false;
1581
				}
1582
			}
1583
1584
			return true;
1585
		}
1586
1587
		/**
1588
		 * Get collection of all active plugins.
1589
		 *
1590
		 * @author Vova Feldman (@svovaf)
1591
		 * @since  1.0.9
1592
		 *
1593
		 * @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...
1594
		 */
1595
		private static function get_active_plugins() {
1596
			self::require_plugin_essentials();
1597
1598
			$active_plugin            = array();
1599
			$all_plugins              = get_plugins();
1600
			$active_plugins_basenames = get_option( 'active_plugins' );
1601
1602
			foreach ( $active_plugins_basenames as $plugin_basename ) {
1603
				$active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ];
1604
			}
1605
1606
			return $active_plugin;
1607
		}
1608
1609
		/**
1610
		 * Get collection of all plugins.
1611
		 *
1612
		 * @author Vova Feldman (@svovaf)
1613
		 * @since  1.1.8
1614
		 *
1615
		 * @return array Key is the plugin file path and the value is an array of the plugin data.
1616
		 */
1617
		private static function get_all_plugins() {
1618
			self::require_plugin_essentials();
1619
1620
			$all_plugins              = get_plugins();
1621
			$active_plugins_basenames = get_option( 'active_plugins' );
1622
1623
			foreach ( $all_plugins as $basename => &$data ) {
1624
				// By default set to inactive (next foreach update the active plugins).
1625
				$data['is_active'] = false;
1626
				// Enrich with plugin slug.
1627
				$data['slug'] = self::get_plugin_slug( $basename );
1628
			}
1629
1630
			// Flag active plugins.
1631
			foreach ( $active_plugins_basenames as $basename ) {
1632
				if ( isset( $all_plugins[ $basename ] ) ) {
1633
					$all_plugins[ $basename ]['is_active'] = true;
1634
				}
1635
			}
1636
1637
			return $all_plugins;
1638
		}
1639
1640
1641
		/**
1642
		 * Cached result of get_site_transient( 'update_plugins' )
1643
		 *
1644
		 * @author Vova Feldman (@svovaf)
1645
		 * @since  1.1.8
1646
		 *
1647
		 * @var object
1648
		 */
1649
		private static $_plugins_info;
1650
1651
		/**
1652
		 * Helper function to get specified plugin's slug.
1653
		 *
1654
		 * @author Vova Feldman (@svovaf)
1655
		 * @since  1.1.8
1656
		 *
1657
		 * @param $basename
1658
		 *
1659
		 * @return string
1660
		 */
1661
		private static function get_plugin_slug( $basename ) {
1662
			if ( ! isset( self::$_plugins_info ) ) {
1663
				self::$_plugins_info = get_site_transient( 'update_plugins' );
1664
			}
1665
1666
			$slug = '';
1667
1668
			if ( is_object( self::$_plugins_info ) ) {
1669
				if ( isset( self::$_plugins_info->no_update ) &&
1670
				     isset( self::$_plugins_info->no_update[ $basename ] ) &&
1671
				     ! empty( self::$_plugins_info->no_update[ $basename ]->slug )
1672
				) {
1673
					$slug = self::$_plugins_info->no_update[ $basename ]->slug;
1674
				} else if ( isset( self::$_plugins_info->response ) &&
1675
				            isset( self::$_plugins_info->response[ $basename ] ) &&
1676
				            ! empty( self::$_plugins_info->response[ $basename ]->slug )
1677
				) {
1678
					$slug = self::$_plugins_info->response[ $basename ]->slug;
1679
				}
1680
			}
1681
1682
			if ( empty( $slug ) ) {
1683
				// Try to find slug from FS data.
1684
				$slug = self::find_slug_by_basename( $basename );
1685
			}
1686
1687
			if ( empty( $slug ) ) {
1688
				// Fallback to plugin's folder name.
1689
				$slug = dirname( $basename );
1690
			}
1691
1692
			return $slug;
1693
		}
1694
1695
		private static $_statics_loaded = false;
1696
1697
		/**
1698
		 * Load static resources.
1699
		 *
1700
		 * @author Vova Feldman (@svovaf)
1701
		 * @since  1.0.1
1702
		 */
1703
		private static function _load_required_static() {
1704
			if ( self::$_statics_loaded ) {
1705
				return;
1706
			}
1707
1708
			self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
1709
1710
			self::$_static_logger->entrance();
1711
1712
			self::$_accounts = FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true );
1713
1714
			self::$_global_admin_notices = FS_Admin_Notice_Manager::instance( 'global' );
1715
1716
			add_action( 'admin_menu', array( 'Freemius', '_add_debug_section' ) );
1717
1718
			add_action( "wp_ajax_fs_toggle_debug_mode", array( 'Freemius', '_toggle_debug_mode' ) );
1719
1720
			self::add_ajax_action_static( 'get_debug_log', array( 'Freemius', '_get_debug_log' ) );
1721
1722
			self::add_ajax_action_static( 'get_db_option', array( 'Freemius', '_get_db_option' ) );
1723
1724
			self::add_ajax_action_static( 'set_db_option', array( 'Freemius', '_set_db_option' ) );
1725
1726
			if ( 0 == did_action( 'plugins_loaded' ) ) {
1727
				add_action( 'plugins_loaded', array( 'Freemius', '_load_textdomain' ), 1 );
1728
			}
1729
1730
			self::$_statics_loaded = true;
1731
		}
1732
1733
		#----------------------------------------------------------------------------------
1734
		#region Localization
1735
		#----------------------------------------------------------------------------------
1736
1737
		/**
1738
		 * Load framework's text domain.
1739
		 *
1740
		 * @author Vova Feldman (@svovaf)
1741
		 * @since  1.2.1
1742
		 */
1743
		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...
1744
			if ( ! is_admin() ) {
1745
				return;
1746
			}
1747
1748
			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...
1749
1750
			// Works both for plugins and themes.
1751
			load_plugin_textdomain(
1752
				'freemius',
1753
				false,
1754
				$fs_active_plugins->newest->sdk_path . '/languages/'
1755
			);
1756
		}
1757
1758
		#endregion
1759
1760
		#----------------------------------------------------------------------------------
1761
		#region Debugging
1762
		#----------------------------------------------------------------------------------
1763
1764
		/**
1765
		 * @author Vova Feldman (@svovaf)
1766
		 * @since  1.0.8
1767
		 */
1768
		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...
1769
			if ( ! current_user_can( 'activate_plugins' )
1770
			     && ! current_user_can( 'switch_themes' )
1771
			) {
1772
				return;
1773
			}
1774
1775
			self::$_static_logger->entrance();
1776
1777
			$title = sprintf( '%s [v.%s]', fs_text( 'freemius-debug' ), WP_FS__SDK_VERSION );
1778
1779
			if ( WP_FS__DEV_MODE ) {
1780
				// Add top-level debug menu item.
1781
				$hook = FS_Admin_Menu_Manager::add_page(
1782
					$title,
1783
					$title,
1784
					'manage_options',
1785
					'freemius',
1786
					array( 'Freemius', '_debug_page_render' )
1787
				);
1788
			} else {
1789
				// Add hidden debug page.
1790
				$hook = FS_Admin_Menu_Manager::add_subpage(
1791
					null,
1792
					$title,
1793
					$title,
1794
					'manage_options',
1795
					'freemius',
1796
					array( 'Freemius', '_debug_page_render' )
1797
				);
1798
			}
1799
1800
			if ( ! empty( $hook ) ) {
1801
				add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) );
1802
			}
1803
		}
1804
1805
		/**
1806
		 * @author Vova Feldman (@svovaf)
1807
		 * @since  1.1.7.3
1808
		 */
1809
		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...
1810
			$is_on = fs_request_get( 'is_on', false, 'post' );
1811
1812
			if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) {
1813
				update_option( 'fs_debug_mode', $is_on );
1814
1815
				// Turn on/off storage logging.
1816
				FS_Logger::_set_storage_logging( ( 1 == $is_on ) );
1817
			}
1818
1819
			exit;
1820
		}
1821
1822
		/**
1823
		 * @author Vova Feldman (@svovaf)
1824
		 * @since  1.2.1.6
1825
		 */
1826
		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...
1827
			$logs = FS_Logger::load_db_logs(
1828
				fs_request_get( 'filters', false, 'post' ),
1829
				! empty( $_POST['limit'] ) && is_numeric( $_POST['limit'] ) ? $_POST['limit'] : 200,
1830
				! empty( $_POST['offset'] ) && is_numeric( $_POST['offset'] ) ? $_POST['offset'] : 0
1831
			);
1832
1833
			self::shoot_ajax_success( $logs );
1834
		}
1835
1836
		/**
1837
		 * @author Vova Feldman (@svovaf)
1838
		 * @since  1.2.1.7
1839
		 */
1840
		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...
1841
			$option_name = fs_request_get( 'option_name' );
1842
1843
			$value = get_option( $option_name );
1844
1845
			$result = array(
1846
				'name' => $option_name,
1847
			);
1848
1849
			if ( false !== $value ) {
1850
				if ( ! is_string( $value ) ) {
1851
					$value = json_encode( $value );
1852
				}
1853
1854
				$result['value'] = $value;
1855
			}
1856
1857
			self::shoot_ajax_success( $result );
1858
		}
1859
1860
		/**
1861
		 * @author Vova Feldman (@svovaf)
1862
		 * @since  1.2.1.7
1863
		 */
1864
		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...
1865
			$option_name  = fs_request_get( 'option_name' );
1866
			$option_value = fs_request_get( 'option_value' );
1867
1868
			if ( ! empty( $option_value ) ) {
1869
				update_option( $option_name, $option_value );
1870
			}
1871
1872
			self::shoot_ajax_success();
1873
		}
1874
1875
		/**
1876
		 * @author Vova Feldman (@svovaf)
1877
		 * @since  1.0.8
1878
		 */
1879
		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...
1880
			self::_clean_admin_content_section();
1881
1882
			if ( fs_request_is_action( 'restart_freemius' ) ) {
1883
				check_admin_referer( 'restart_freemius' );
1884
1885
				// Clear accounts data.
1886
				self::$_accounts->clear( true );
1887
1888
				// Clear SDK reference cache.
1889
				delete_option( 'fs_active_plugins' );
1890
			} else if ( fs_request_is_action( 'simulate_trial' ) ) {
1891
				check_admin_referer( 'simulate_trial' );
1892
1893
				$fs = freemius( fs_request_get( 'module_id' ) );
1894
1895
				// Update SDK install to at least 24 hours before.
1896
				$fs->_storage->install_timestamp = ( time() - WP_FS__TIME_24_HOURS_IN_SEC );
1897
				// Unset the trial shown timestamp.
1898
				unset( $fs->_storage->trial_promotion_shown );
1899
			} else if ( fs_request_is_action( 'delete_install' ) ) {
1900
				check_admin_referer( 'delete_install' );
1901
1902
				self::_delete_site_by_slug(
1903
					fs_request_get( 'slug' ),
1904
					fs_request_get( 'module_type' )
1905
				);
1906
			} else if ( fs_request_is_action( 'download_logs' ) ) {
1907
				check_admin_referer( 'download_logs' );
1908
1909
				$download_url = FS_Logger::download_db_logs(
1910
					fs_request_get( 'filters', false, 'post' )
1911
				);
1912
1913
				if ( false === $download_url ) {
1914
					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].' );
1915
				}
1916
1917
				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 1909 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...
1918
			}
1919
		}
1920
1921
		/**
1922
		 * @author Vova Feldman (@svovaf)
1923
		 * @since  1.0.8
1924
		 */
1925
		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...
1926
			self::$_static_logger->entrance();
1927
1928
			$vars = array(
1929
				'plugin_sites'    => self::get_all_sites(),
1930
				'theme_sites'     => self::get_all_sites( WP_FS__MODULE_TYPE_THEME ),
1931
				'users'           => self::get_all_users(),
1932
				'addons'          => self::get_all_addons(),
1933
				'account_addons'  => self::get_all_account_addons(),
1934
				'plugin_licenses' => self::get_all_licenses(),
1935
				'theme_licenses'  => self::get_all_licenses( WP_FS__MODULE_TYPE_THEME )
1936
			);
1937
1938
			fs_enqueue_local_style( 'fs_debug', '/admin/debug.css' );
1939
			fs_require_once_template( 'debug.php', $vars );
1940
		}
1941
1942
		#endregion
1943
1944
		#----------------------------------------------------------------------------------
1945
		#region Connectivity Issues
1946
		#----------------------------------------------------------------------------------
1947
1948
		/**
1949
		 * Check if Freemius should be turned on for the current plugin install.
1950
		 *
1951
		 * Note:
1952
		 *  $this->_is_on is updated in has_api_connectivity()
1953
		 *
1954
		 * @author Vova Feldman (@svovaf)
1955
		 * @since  1.0.9
1956
		 *
1957
		 * @return bool
1958
		 */
1959
		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...
1960
			self::$_static_logger->entrance();
1961
1962
			if ( isset( $this->_is_on ) ) {
1963
				return $this->_is_on;
1964
			}
1965
1966
			// If already installed or pending then sure it's on :)
1967
			if ( $this->is_registered() || $this->is_pending_activation() ) {
1968
				$this->_is_on = true;
1969
1970
				return true;
1971
			}
1972
1973
			return false;
1974
		}
1975
1976
		/**
1977
		 * @author Vova Feldman (@svovaf)
1978
		 * @since  1.1.7.3
1979
		 *
1980
		 * @param bool $flush_if_no_connectivity
1981
		 *
1982
		 * @return bool
1983
		 */
1984
		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...
1985
			if ( ! isset( $this->_storage->connectivity_test ) ) {
1986
				// Connectivity test was never executed, or cache was cleared.
1987
				return true;
1988
			}
1989
1990
			if ( WP_FS__PING_API_ON_IP_OR_HOST_CHANGES ) {
1991
				if ( WP_FS__IS_HTTP_REQUEST ) {
1992
					if ( $_SERVER['HTTP_HOST'] != $this->_storage->connectivity_test['host'] ) {
1993
						// Domain changed.
1994
						return true;
1995
					}
1996
1997
					if ( WP_FS__REMOTE_ADDR != $this->_storage->connectivity_test['server_ip'] ) {
1998
						// Server IP changed.
1999
						return true;
2000
					}
2001
				}
2002
			}
2003
2004
			if ( $this->_storage->connectivity_test['is_connected'] &&
2005
			     $this->_storage->connectivity_test['is_active']
2006
			) {
2007
				// API connected and Freemius is active - no need to run connectivity check.
2008
				return false;
2009
			}
2010
2011
			if ( $flush_if_no_connectivity ) {
2012
				/**
2013
				 * If explicitly asked to flush when no connectivity - do it only
2014
				 * if at least 10 sec passed from the last API connectivity test.
2015
				 */
2016
				return ( isset( $this->_storage->connectivity_test['timestamp'] ) &&
2017
				         ( WP_FS__SCRIPT_START_TIME - $this->_storage->connectivity_test['timestamp'] ) > 10 );
2018
			}
2019
2020
			/**
2021
			 * @since 1.1.7 Don't check for connectivity on plugin downgrade.
2022
			 */
2023
			$version = $this->get_plugin_version();
2024
			if ( version_compare( $version, $this->_storage->connectivity_test['version'], '>' ) ) {
2025
				// If it's a plugin version upgrade and Freemius is off or no connectivity, run connectivity test.
2026
				return true;
2027
			}
2028
2029
			return false;
2030
		}
2031
2032
		/**
2033
		 * @author Vova Feldman (@svovaf)
2034
		 * @since  1.1.7.4
2035
		 *
2036
		 * @return object|false
2037
		 */
2038
		private function ping() {
2039
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) {
2040
				return false;
2041
			}
2042
2043
			$version = $this->get_plugin_version();
2044
2045
			$is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() );
2046
2047
			return $this->get_api_plugin_scope()->ping(
2048
				$this->get_anonymous_id(),
2049
				array(
2050
					'is_update' => json_encode( $is_update ),
2051
					'version'   => $version,
2052
					'sdk'       => $this->version,
2053
					'is_admin'  => json_encode( is_admin() ),
2054
					'is_ajax'   => json_encode( self::is_ajax() ),
2055
					'is_cron'   => json_encode( self::is_cron() ),
2056
					'is_http'   => json_encode( WP_FS__IS_HTTP_REQUEST ),
2057
				)
2058
			);
2059
		}
2060
2061
		/**
2062
		 * Check if there's any connectivity issue to Freemius API.
2063
		 *
2064
		 * @author Vova Feldman (@svovaf)
2065
		 * @since  1.0.9
2066
		 *
2067
		 * @param bool $flush_if_no_connectivity
2068
		 *
2069
		 * @return bool
2070
		 */
2071
		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...
2072
			$this->_logger->entrance();
2073
2074
			if ( isset( $this->_has_api_connection ) && ( $this->_has_api_connection || ! $flush_if_no_connectivity ) ) {
2075
				return $this->_has_api_connection;
2076
			}
2077
2078
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY &&
2079
			     isset( $this->_storage->connectivity_test ) &&
2080
			     true === $this->_storage->connectivity_test['is_connected']
2081
			) {
2082
				unset( $this->_storage->connectivity_test );
2083
			}
2084
2085
			if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) {
2086
				$this->_has_api_connection = $this->_storage->connectivity_test['is_connected'];
2087
				/**
2088
				 * @since 1.1.6 During dev mode, if there's connectivity - turn Freemius on regardless the configuration.
2089
				 *
2090
				 * @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.
2091
				 */
2092
				$this->_is_on = $this->_storage->connectivity_test['is_active'] ||
2093
				                $this->is_premium() ||
2094
				                ( WP_FS__DEV_MODE && $this->_has_api_connection && ! WP_FS__SIMULATE_FREEMIUS_OFF );
2095
2096
				return $this->_has_api_connection;
2097
			}
2098
2099
			$pong         = $this->ping();
2100
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2101
2102
			if ( ! $is_connected ) {
2103
				// API failure.
2104
				$this->_add_connectivity_issue_message( $pong );
2105
			}
2106
2107
			$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 2099 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...
2108
2109
			return $this->_has_api_connection;
2110
		}
2111
2112
		/**
2113
		 * @author Vova Feldman (@svovaf)
2114
		 * @since  1.1.7.4
2115
		 *
2116
		 * @param object $pong
2117
		 * @param bool   $is_connected
2118
		 */
2119
		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...
2120
			$this->_logger->entrance();
2121
2122
			$version = $this->get_plugin_version();
2123
2124
			if ( ! $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) {
2125
				$is_active = false;
2126
			} else {
2127
				$is_active = ( isset( $pong->is_active ) && true == $pong->is_active );
2128
			}
2129
2130
			$is_active = $this->apply_filters(
2131
				'is_on',
2132
				$is_active,
2133
				$this->is_plugin_update(),
2134
				$version
2135
			);
2136
2137
			$this->_storage->connectivity_test = array(
2138
				'is_connected' => $is_connected,
2139
				'host'         => $_SERVER['HTTP_HOST'],
2140
				'server_ip'    => WP_FS__REMOTE_ADDR,
2141
				'is_active'    => $is_active,
2142
				'timestamp'    => WP_FS__SCRIPT_START_TIME,
2143
				// Last version with connectivity attempt.
2144
				'version'      => $version,
2145
			);
2146
2147
			$this->_has_api_connection = $is_connected;
2148
			$this->_is_on              = $is_active || ( WP_FS__DEV_MODE && $is_connected && ! WP_FS__SIMULATE_FREEMIUS_OFF );
2149
		}
2150
2151
		/**
2152
		 * Force turning Freemius on.
2153
		 *
2154
		 * @author Vova Feldman (@svovaf)
2155
		 * @since  1.1.8.1
2156
		 *
2157
		 * @return bool TRUE if successfully turned on.
2158
		 */
2159
		private function turn_on() {
2160
			$this->_logger->entrance();
2161
2162
			if ( $this->is_on() || ! isset( $this->_storage->connectivity_test['is_active'] ) ) {
2163
				return false;
2164
			}
2165
2166
			$updated_connectivity              = $this->_storage->connectivity_test;
2167
			$updated_connectivity['is_active'] = true;
2168
			$updated_connectivity['timestamp'] = WP_FS__SCRIPT_START_TIME;
2169
			$this->_storage->connectivity_test = $updated_connectivity;
2170
2171
			$this->_is_on = true;
2172
2173
			return true;
2174
		}
2175
2176
		/**
2177
		 * Anonymous and unique site identifier (Hash).
2178
		 *
2179
		 * @author Vova Feldman (@svovaf)
2180
		 * @since  1.1.0
2181
		 *
2182
		 * @return string
2183
		 */
2184
		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...
2185
			$unique_id = self::$_accounts->get_option( 'unique_id' );
2186
2187
			if ( empty( $unique_id ) || ! is_string( $unique_id ) ) {
2188
				$key = get_site_url();
2189
2190
				// If localhost, assign microtime instead of domain.
2191
				if ( WP_FS__IS_LOCALHOST ||
2192
				     false !== strpos( $key, 'localhost' ) ||
2193
				     false === strpos( $key, '.' )
2194
				) {
2195
					$key = microtime();
2196
				}
2197
2198
				$unique_id = md5( $key );
2199
2200
				self::$_accounts->set_option( 'unique_id', $unique_id, true );
2201
			}
2202
2203
			$this->_logger->departure( $unique_id );
2204
2205
			return $unique_id;
2206
		}
2207
2208
		/**
2209
		 * @author Vova Feldman (@svovaf)
2210
		 * @since  1.1.7.4
2211
		 *
2212
		 * @return \WP_User
2213
		 */
2214
		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...
2215
			self::require_pluggable_essentials();
2216
2217
			return wp_get_current_user();
2218
		}
2219
2220
		/**
2221
		 * @author Vova Feldman (@svovaf)
2222
		 * @since  1.2.1.7
2223
		 *
2224
		 * @param string $email
2225
		 *
2226
		 * @return bool
2227
		 */
2228
		static function is_valid_email( $email ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2229
			if ( false === filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
2230
				return false;
2231
			}
2232
2233
			$parts = explode( '@', $email );
2234
2235
			if ( 2 !== count( $parts ) || empty( $parts[1] ) ) {
2236
				return false;
2237
			}
2238
2239
			$blacklist = array(
2240
				'admin.',
2241
				'webmaster.',
2242
				'localhost.',
2243
				'dev.',
2244
				'development.',
2245
				'test.',
2246
				'stage.',
2247
				'staging.',
2248
			);
2249
2250
			// Make sure domain is not one of the blacklisted.
2251
			foreach ( $blacklist as $invalid ) {
2252
				if ( 0 === strpos( $parts[1], $invalid ) ) {
2253
					return false;
2254
				}
2255
			}
2256
2257
			// Get the UTF encoded domain name.
2258
			$domain = idn_to_ascii( $parts[1] ) . '.';
2259
2260
			return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) );
2261
		}
2262
		
2263
		/**
2264
		 * Generate API connectivity issue message.
2265
		 *
2266
		 * @author Vova Feldman (@svovaf)
2267
		 * @since  1.0.9
2268
		 *
2269
		 * @param mixed $api_result
2270
		 * @param bool  $is_first_failure
2271
		 */
2272
		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...
2273
			if ( ! $this->is_premium() && $this->_enable_anonymous ) {
2274
				// Don't add message if it's the free version and can run anonymously.
2275
				return;
2276
			}
2277
2278
			if ( ! function_exists( 'wp_nonce_url' ) ) {
2279
				require_once ABSPATH . 'wp-includes/functions.php';
2280
			}
2281
2282
			$current_user = self::_get_current_wp_user();
2283
//			$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...
2284
			$admin_email = $current_user->user_email;
2285
2286
			$message = false;
2287
			if ( is_object( $api_result ) &&
2288
			     isset( $api_result->error ) &&
2289
			     isset( $api_result->error->code )
2290
			) {
2291
				switch ( $api_result->error->code ) {
2292
					case 'curl_missing':
2293
						$missing_methods = '';
2294
						if ( is_array( $api_result->missing_methods ) &&
2295
						     ! empty( $api_result->missing_methods )
2296
						) {
2297
							foreach ( $api_result->missing_methods as $m ) {
2298
								if ( 'curl_version' === $m ) {
2299
									continue;
2300
								}
2301
2302
								if ( ! empty( $missing_methods ) ) {
2303
									$missing_methods .= ', ';
2304
								}
2305
2306
								$missing_methods .= sprintf( '<code>%s</code>', $m );
2307
							}
2308
2309
							if ( ! empty( $missing_methods ) ) {
2310
								$missing_methods = sprintf(
2311
									'<br><br><b>%s</b> %s',
2312
									$this->get_text( 'curl-disabled-methods' ),
2313
									$missing_methods
2314
								);
2315
							}
2316
						}
2317
2318
						$message = sprintf(
2319
							$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2320
							$this->get_text( 'curl-missing-message' ) . ' ' .
2321
							$missing_methods .
2322
							' %s',
2323
							'<b>' . $this->get_plugin_name() . '</b>',
2324
							sprintf(
2325
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2326
								sprintf(
2327
									'<a class="fs-resolve" data-type="curl" href="#"><b>%s</b></a>%s',
2328
									$this->get_text( 'curl-missing-no-clue-title' ),
2329
									' - ' . sprintf(
2330
										$this->get_text( 'curl-missing-no-clue-desc' ),
2331
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2332
									)
2333
								),
2334
								sprintf(
2335
									'<b>%s</b> - %s',
2336
									$this->get_text( 'sysadmin-title' ),
2337
									sprintf( $this->get_text( 'curl-missing-sysadmin-desc' ), $this->_module_type )
2338
								),
2339
								sprintf(
2340
									'<a href="%s"><b>%s</b></a>%s',
2341
									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 ),
2342
									$this->get_text( 'deactivate-plugin-title' ),
2343
									' - ' . $this->get_text( 'deactivate-plugin-desc' )
2344
								)
2345
							)
2346
						);
2347
						break;
2348
					case 'cloudflare_ddos_protection':
2349
						$message = sprintf(
2350
							$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2351
							$this->get_text( 'cloudflare-blocks-connection-message' ) . ' ' .
2352
							$this->get_text( 'happy-to-resolve-issue-asap' ) .
2353
							' %s',
2354
							'<b>' . $this->get_plugin_name() . '</b>',
2355
							sprintf(
2356
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2357
								sprintf(
2358
									'<a class="fs-resolve" data-type="cloudflare" href="#"><b>%s</b></a>%s',
2359
									$this->get_text( 'fix-issue-title' ),
2360
									' - ' . sprintf(
2361
										$this->get_text( 'fix-issue-desc' ),
2362
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2363
									)
2364
								),
2365
								sprintf(
2366
									'<a href="%s" target="_blank"><b>%s</b></a>%s',
2367
									sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
2368
									$this->get_text( 'install-previous-title' ),
2369
									' - ' . $this->get_text( 'install-previous-desc' )
2370
								),
2371
								sprintf(
2372
									'<a href="%s"><b>%s</b></a>%s',
2373
									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 ),
2374
									$this->get_text( 'deactivate-plugin-title' ),
2375
									' - ' . $this->get_text( 'deactivate-plugin-desc' )
2376
								)
2377
							)
2378
						);
2379
						break;
2380
					case 'squid_cache_block':
2381
						$message = sprintf(
2382
							$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2383
							$this->get_text( 'squid-blocks-connection-message' ) .
2384
							' %s',
2385
							'<b>' . $this->get_plugin_name() . '</b>',
2386
							sprintf(
2387
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2388
								sprintf(
2389
									'<a class="fs-resolve" data-type="squid" href="#"><b>%s</b></a>%s',
2390
									$this->get_text( 'squid-no-clue-title' ),
2391
									' - ' . sprintf(
2392
										$this->get_text( 'squid-no-clue-desc' ),
2393
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2394
									)
2395
								),
2396
								sprintf(
2397
									'<b>%s</b> - %s',
2398
									$this->get_text( 'sysadmin-title' ),
2399
									sprintf(
2400
										$this->get_text( 'squid-sysadmin-desc' ),
2401
										// We use a filter since the plugin might require additional API connectivity.
2402
										'<b>' . implode( ', ', $this->apply_filters( 'api_domains', array( 'api.freemius.com', 'wp.freemius.com' ) ) ) . '</b>',
2403
										$this->_module_type
2404
									)
2405
								),
2406
								sprintf(
2407
									'<a href="%s"><b>%s</b></a>%s',
2408
									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 ),
2409
									$this->get_text( 'deactivate-plugin-title' ),
2410
									' - ' . $this->get_text( 'deactivate-plugin-desc' )
2411
								)
2412
							)
2413
						);
2414
						break;
2415
//					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...
2416
//						$message = $this->get_text( 'connectivity-test-fails-message' );
2417
//						break;
2418
				}
2419
			}
2420
2421
			$message_id = 'failed_connect_api';
2422
			$type       = 'error';
2423
2424
			if ( false === $message ) {
2425
				if ( $is_first_failure ) {
2426
					// First attempt failed.
2427
					$message = sprintf(
2428
						$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2429
						$this->get_text( 'connectivity-test-fails-message' ) . ' ' .
2430
						$this->get_text( 'connectivity-test-maybe-temporary' ) . '<br><br>' .
2431
						'%s',
2432
						'<b>' . $this->get_plugin_name() . '</b>',
2433
						sprintf(
2434
							'<div id="fs_firewall_issue_options">%s %s</div>',
2435
							sprintf(
2436
								'<a  class="button button-primary fs-resolve" data-type="retry_ping" href="#">%s</a>',
2437
								$this->get_text( 'yes-do-your-thing' )
2438
							),
2439
							sprintf(
2440
								'<a href="%s" class="button">%s</a>',
2441
								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 ),
2442
								$this->get_text( 'no-deactivate' )
2443
							)
2444
						)
2445
					);
2446
2447
					$message_id = 'failed_connect_api_first';
2448
					$type       = 'promotion';
2449
				} else {
2450
					// Second connectivity attempt failed.
2451
					$message = sprintf(
2452
						$this->get_text( 'x-requires-access-to-api' ) . ' ' .
2453
						$this->get_text( 'connectivity-test-fails-message' ) . ' ' .
2454
						$this->get_text( 'happy-to-resolve-issue-asap' ) .
2455
						' %s',
2456
						'<b>' . $this->get_plugin_name() . '</b>',
2457
						sprintf(
2458
							'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
2459
							sprintf(
2460
								'<a class="fs-resolve" data-type="general" href="#"><b>%s</b></a>%s',
2461
								$this->get_text( 'fix-issue-title' ),
2462
								' - ' . sprintf(
2463
									$this->get_text( 'fix-issue-desc' ),
2464
									'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2465
								)
2466
							),
2467
							sprintf(
2468
								'<a href="%s" target="_blank"><b>%s</b></a>%s',
2469
								sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
2470
								$this->get_text( 'install-previous-title' ),
2471
								' - ' . $this->get_text( 'install-previous-desc' )
2472
							),
2473
							sprintf(
2474
								'<a href="%s"><b>%s</b></a>%s',
2475
								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 ),
2476
								$this->get_text( 'deactivate-plugin-title' ),
2477
								' - ' . $this->get_text( 'deactivate-plugin-desc' )
2478
							)
2479
						)
2480
					);
2481
				}
2482
			}
2483
2484
			$this->_admin_notices->add_sticky(
2485
				$message,
2486
				$message_id,
2487
				$this->get_text( 'oops' ) . '...',
2488
				$type
2489
			);
2490
		}
2491
2492
		/**
2493
		 * Handle user request to resolve connectivity issue.
2494
		 * This method will send an email to Freemius API technical staff for resolution.
2495
		 * The email will contain server's info and installed plugins (might be caching issue).
2496
		 *
2497
		 * @author Vova Feldman (@svovaf)
2498
		 * @since  1.0.9
2499
		 */
2500
		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...
2501
			$this->_admin_notices->remove_sticky( 'failed_connect_api' );
2502
2503
			$pong = $this->ping();
2504
2505
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2506
2507
			if ( $is_connected ) {
2508
				$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 2503 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...
2509
2510
				echo $this->get_after_plugin_activation_redirect_url();
2511
				exit;
2512
			}
2513
2514
			$current_user = self::_get_current_wp_user();
2515
			$admin_email  = $current_user->user_email;
2516
2517
			$error_type = fs_request_get( 'error_type', 'general' );
2518
2519
			switch ( $error_type ) {
2520
				case 'squid':
2521
					$title = 'Squid ACL Blocking Issue';
2522
					break;
2523
				case 'cloudflare':
2524
					$title = 'CloudFlare Blocking Issue';
2525
					break;
2526
				default:
2527
					$title = 'API Connectivity Issue';
2528
					break;
2529
			}
2530
2531
			$custom_email_sections = array();
2532
2533
			// Add 'API Error' custom email section.
2534
			$custom_email_sections['api_error'] = array(
2535
				'title' => 'API Error',
2536
				'rows'  => array(
2537
					'ping' => array(
2538
						'API Error',
2539
						is_string( $pong ) ? htmlentities( $pong ) : json_encode( $pong )
2540
					),
2541
				)
2542
			);
2543
2544
			// Send email with technical details to resolve API connectivity issues.
2545
			$this->send_email(
2546
				'[email protected]',                              // recipient
2547
				$title . ' [' . $this->get_plugin_name() . ']',  // subject
2548
				$custom_email_sections,
2549
				array( "Reply-To: $admin_email <$admin_email>" ) // headers
2550
			);
2551
2552
			$this->_admin_notices->add_sticky(
2553
				sprintf(
2554
					$this->get_text( 'fix-request-sent-message' ),
2555
					'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
2556
				),
2557
				'server_details_sent'
2558
			);
2559
2560
			// Action was taken, tell that API connectivity troubleshooting should be off now.
2561
2562
			echo "1";
2563
			exit;
2564
		}
2565
2566
		/**
2567
		 * Handle connectivity test retry approved by the user.
2568
		 *
2569
		 * @author Vova Feldman (@svovaf)
2570
		 * @since  1.1.7.4
2571
		 */
2572
		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...
2573
			$this->_admin_notices->remove_sticky( 'failed_connect_api_first' );
2574
2575
			$pong = $this->ping();
2576
2577
			$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
2578
2579
			if ( $is_connected ) {
2580
				$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 2575 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...
2581
2582
				echo $this->get_after_plugin_activation_redirect_url();
2583
			} else {
2584
				// Add connectivity issue message after 2nd failed attempt.
2585
				$this->_add_connectivity_issue_message( $pong, false );
2586
2587
				echo "1";
2588
			}
2589
2590
			exit;
2591
		}
2592
2593
		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...
2594
			$params = array();
2595
			fs_require_once_template( 'firewall-issues-js.php', $params );
2596
		}
2597
2598
		#endregion
2599
2600
		#----------------------------------------------------------------------------------
2601
		#region Email
2602
		#----------------------------------------------------------------------------------
2603
2604
		/**
2605
		 * Generates and sends an HTML email with customizable sections.
2606
		 *
2607
		 * @author Leo Fajardo (@leorw)
2608
		 * @since  1.1.2
2609
		 *
2610
		 * @param string $to_address
2611
		 * @param string $subject
2612
		 * @param array  $sections
2613
		 * @param array  $headers
2614
		 *
2615
		 * @return bool Whether the email contents were sent successfully.
2616
		 */
2617
		private function send_email(
2618
			$to_address,
2619
			$subject,
2620
			$sections = array(),
2621
			$headers = array()
2622
		) {
2623
			$default_sections = $this->get_email_sections();
2624
2625
			// Insert new sections or replace the default email sections.
2626
			if ( is_array( $sections ) && ! empty( $sections ) ) {
2627
				foreach ( $sections as $section_id => $custom_section ) {
2628
					if ( ! isset( $default_sections[ $section_id ] ) ) {
2629
						// If the section does not exist, add it.
2630
						$default_sections[ $section_id ] = $custom_section;
2631
					} else {
2632
						// If the section already exists, override it.
2633
						$current_section = $default_sections[ $section_id ];
2634
2635
						// Replace the current section's title if a custom section title exists.
2636
						if ( isset( $custom_section['title'] ) ) {
2637
							$current_section['title'] = $custom_section['title'];
2638
						}
2639
2640
						// Insert new rows under the current section or replace the default rows.
2641
						if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) {
2642
							foreach ( $custom_section['rows'] as $row_id => $row ) {
2643
								$current_section['rows'][ $row_id ] = $row;
2644
							}
2645
						}
2646
2647
						$default_sections[ $section_id ] = $current_section;
2648
					}
2649
				}
2650
			}
2651
2652
			$vars    = array( 'sections' => $default_sections );
2653
			$message = fs_get_template( 'email.php', $vars );
2654
2655
			// Set the type of email to HTML.
2656
			$headers[] = 'Content-type: text/html; charset=UTF-8';
2657
2658
			$header_string = implode( "\r\n", $headers );
2659
2660
			return wp_mail(
2661
				$to_address,
2662
				$subject,
2663
				$message,
2664
				$header_string
2665
			);
2666
		}
2667
2668
		/**
2669
		 * Generates the data for the sections of the email content.
2670
		 *
2671
		 * @author Leo Fajardo (@leorw)
2672
		 * @since  1.1.2
2673
		 *
2674
		 * @return array
2675
		 */
2676
		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...
2677
			// Retrieve the current user's information so that we can get the user's email, first name, and last name below.
2678
			$current_user = self::_get_current_wp_user();
2679
2680
			// Retrieve the cURL version information so that we can get the version number below.
2681
			$curl_version_information = curl_version();
2682
2683
			$active_plugin = self::get_active_plugins();
2684
2685
			// Generate the list of active plugins separated by new line.
2686
			$active_plugin_string = '';
2687
			foreach ( $active_plugin as $plugin ) {
2688
				$active_plugin_string .= sprintf(
2689
					'<a href="%s">%s</a> [v%s]<br>',
2690
					$plugin['PluginURI'],
2691
					$plugin['Name'],
2692
					$plugin['Version']
2693
				);
2694
			}
2695
2696
			$server_ip = WP_FS__REMOTE_ADDR;
2697
2698
			// Add PHP info for deeper investigation.
2699
			ob_start();
2700
			phpinfo();
2701
			$php_info = ob_get_clean();
2702
2703
			$api_domain = substr( FS_API__ADDRESS, strpos( FS_API__ADDRESS, ':' ) + 3 );
2704
2705
			// Generate the default email sections.
2706
			$sections = array(
2707
				'sdk'      => array(
2708
					'title' => 'SDK',
2709
					'rows'  => array(
2710
						'fs_version'   => array( 'FS Version', $this->version ),
2711
						'curl_version' => array( 'cURL Version', $curl_version_information['version'] )
2712
					)
2713
				),
2714
				'plugin'   => array(
2715
					'title' => ucfirst( $this->get_module_type() ),
2716
					'rows'  => array(
2717
						'name'    => array( 'Name', $this->get_plugin_name() ),
2718
						'version' => array( 'Version', $this->get_plugin_version() )
2719
					)
2720
				),
2721
				'api'      => array(
2722
					'title' => 'API Subdomain',
2723
					'rows'  => array(
2724
						'dns' => array(
2725
							'DNS_CNAME',
2726
							function_exists( 'dns_get_record' ) ?
2727
								var_export( dns_get_record( $api_domain, DNS_CNAME ), true ) :
2728
								'dns_get_record() disabled/blocked'
2729
						),
2730
						'ip'  => array(
2731
							'IP',
2732
							function_exists( 'gethostbyname' ) ?
2733
								gethostbyname( $api_domain ) :
2734
								'gethostbyname() disabled/blocked'
2735
						),
2736
					),
2737
				),
2738
				'site'     => array(
2739
					'title' => 'Site',
2740
					'rows'  => array(
2741
						'unique_id'   => array( 'Unique ID', $this->get_anonymous_id() ),
2742
						'address'     => array( 'Address', site_url() ),
2743
						'host'        => array(
2744
							'HTTP_HOST',
2745
							( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' )
2746
						),
2747
						'hosting'     => array(
2748
							'Hosting Company' => fs_request_has( 'hosting_company' ) ?
2749
								fs_request_get( 'hosting_company' ) :
2750
								'Unknown',
2751
						),
2752
						'server_addr' => array(
2753
							'SERVER_ADDR',
2754
							'<a href="http://www.projecthoneypot.org/ip_' . $server_ip . '">' . $server_ip . '</a>'
2755
						)
2756
					)
2757
				),
2758
				'user'     => array(
2759
					'title' => 'User',
2760
					'rows'  => array(
2761
						'email' => array( 'Email', $current_user->user_email ),
2762
						'first' => array( 'First', $current_user->user_firstname ),
2763
						'last'  => array( 'Last', $current_user->user_lastname )
2764
					)
2765
				),
2766
				'plugins'  => array(
2767
					'title' => 'Plugins',
2768
					'rows'  => array(
2769
						'active_plugins' => array( 'Active Plugins', $active_plugin_string )
2770
					)
2771
				),
2772
				'php_info' => array(
2773
					'title' => 'PHP Info',
2774
					'rows'  => array(
2775
						'info' => array( $php_info )
2776
					),
2777
				)
2778
			);
2779
2780
			// Allow the sections to be modified by other code.
2781
			$sections = $this->apply_filters( 'email_template_sections', $sections );
2782
2783
			return $sections;
2784
		}
2785
2786
		#endregion
2787
2788
		#----------------------------------------------------------------------------------
2789
		#region Initialization
2790
		#----------------------------------------------------------------------------------
2791
2792
		/**
2793
		 * Init plugin's Freemius instance.
2794
		 *
2795
		 * @author Vova Feldman (@svovaf)
2796
		 * @since  1.0.1
2797
		 *
2798
		 * @param number $id
2799
		 * @param string $public_key
2800
		 * @param bool   $is_live
2801
		 * @param bool   $is_premium
2802
		 */
2803
		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...
2804
			$this->_logger->entrance();
2805
2806
			$this->dynamic_init( array(
2807
				'id'         => $id,
2808
				'public_key' => $public_key,
2809
				'is_live'    => $is_live,
2810
				'is_premium' => $is_premium,
2811
			) );
2812
		}
2813
2814
		/**
2815
		 * Dynamic initiator, originally created to support initiation
2816
		 * with parent_id for add-ons.
2817
		 *
2818
		 * @author Vova Feldman (@svovaf)
2819
		 * @since  1.0.6
2820
		 *
2821
		 * @param array $plugin_info
2822
		 *
2823
		 * @throws Freemius_Exception
2824
		 */
2825
		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...
2826
			$this->_logger->entrance();
2827
2828
			$this->parse_settings( $plugin_info );
2829
2830
            if ( ! self::is_ajax() ) {
2831
                if ( ! $this->is_addon() || $this->is_only_premium() ) {
2832
                    add_action( 'admin_menu', array( &$this, '_prepare_admin_menu' ), WP_FS__LOWEST_PRIORITY );
2833
                }
2834
            }
2835
2836
            if ( $this->should_stop_execution() ) {
2837
				return;
2838
			}
2839
2840
			if ( ! $this->is_registered() ) {
2841
				if ( $this->is_anonymous() ) {
2842
					// If user skipped, no need to test connectivity.
2843
					$this->_has_api_connection = true;
2844
					$this->_is_on              = true;
2845
				} else {
2846
					if ( ! $this->has_api_connectivity() ) {
2847
						if ( $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) ||
2848
						     $this->_admin_notices->has_sticky( 'failed_connect_api' )
2849
						) {
2850
							if ( ! $this->_enable_anonymous || $this->is_premium() ) {
2851
								// If anonymous mode is disabled, add firewall admin-notice message.
2852
								add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) );
2853
2854
								$this->add_ajax_action( 'resolve_firewall_issues', array(
2855
									&$this,
2856
									'_email_about_firewall_issue'
2857
								) );
2858
2859
								$this->add_ajax_action( 'retry_connectivity_test', array(
2860
									&$this,
2861
									'_retry_connectivity_test'
2862
								) );
2863
							}
2864
						}
2865
2866
						return;
2867
					} else {
2868
						$this->_admin_notices->remove_sticky( array(
2869
							'failed_connect_api_first',
2870
							'failed_connect_api',
2871
						) );
2872
2873
						if ( $this->_anonymous_mode ) {
2874
							// Simulate anonymous mode.
2875
							$this->_is_anonymous = true;
2876
						}
2877
					}
2878
				}
2879
2880
				// Check if Freemius is on for the current plugin.
2881
				// This MUST be executed after all the plugin variables has been loaded.
2882
				if ( ! $this->is_on() ) {
2883
					return;
2884
				}
2885
			}
2886
2887
			if ( $this->has_api_connectivity() ) {
2888
				if ( self::is_cron() ) {
2889
					$this->hook_callback_to_sync_cron();
2890
				} else if ( $this->is_user_in_admin() ) {
2891
					/**
2892
					 * Schedule daily data sync cron if:
2893
					 *
2894
					 *  1. User opted-in (for tracking).
2895
					 *  2. If skipped, but later upgraded (opted-in via upgrade).
2896
					 *
2897
					 * @author Vova Feldman (@svovaf)
2898
					 * @since  1.1.7.3
2899
					 *
2900
					 */
2901
					if ( $this->is_registered() ) {
2902
						if ( ! $this->is_sync_cron_on() && $this->is_tracking_allowed() ) {
2903
							$this->schedule_sync_cron();
2904
						}
2905
					}
2906
2907
					/**
2908
					 * Check if requested for manual blocking background sync.
2909
					 */
2910
					if ( fs_request_has( 'background_sync' ) ) {
2911
						$this->run_manual_sync();
2912
					}
2913
				}
2914
			}
2915
2916
			if ( $this->is_registered() ) {
2917
				$this->hook_callback_to_install_sync();
2918
			}
2919
2920
			if ( $this->is_addon() ) {
2921
				if ( $this->is_parent_plugin_installed() ) {
2922
					// Link to parent FS.
2923
					$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...
2924
2925
					// Get parent plugin reference.
2926
					$this->_parent_plugin = $this->_parent->get_plugin();
2927
				}
2928
			}
2929
2930
			if ( $this->is_user_in_admin() ) {
2931
				if ( self::is_plugins_page() && $this->is_plugin() ) {
2932
					$this->hook_plugin_action_links();
2933
				}
2934
2935
				if ( $this->is_addon() ) {
2936
					if ( ! $this->is_parent_plugin_installed() ) {
2937
						$parent_name = $this->get_option( $plugin_info, 'parent_name', null );
2938
2939
						if ( isset( $plugin_info['parent'] ) ) {
2940
							$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...
2941
						}
2942
2943
						$this->_admin_notices->add(
2944
							( ! empty( $parent_name ) ?
2945
								sprintf( $this->get_text( 'addon-x-cannot-run-without-y' ), $this->get_plugin_name(), $parent_name ) :
2946
								sprintf( $this->get_text( 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() )
2947
							),
2948
							$this->get_text( 'oops' ) . '...',
2949
							'error'
2950
						);
2951
2952
						return;
2953
					} else {
2954
						if ( $this->_parent->is_registered() && ! $this->is_registered() ) {
2955
							// If parent plugin activated, automatically install add-on for the user.
2956
							$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...
2957
						} else if ( ! $this->_parent->is_registered() && $this->is_registered() ) {
2958
							// If add-on activated and parent not, automatically install parent for the user.
2959
							$this->activate_parent_account( $this->_parent );
0 ignored issues
show
Security Bug introduced by
It seems like $this->_parent can also be of type false; however, Freemius::activate_parent_account() does only seem to accept object<Freemius>, did you maybe forget to handle an error condition?
Loading history...
2960
						}
2961
2962
						// @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic.
2963
						if ( $this->is_premium() ) {
2964
							// Remove add-on download admin-notice.
2965
							$this->_parent->_admin_notices->remove_sticky( array(
2966
								'addon_plan_upgraded_' . $this->_slug,
2967
								'no_addon_license_' . $this->_slug,
2968
							) );
2969
						}
2970
2971
//						$this->deactivate_premium_only_addon_without_license();
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2972
					}
2973
				} else {
2974
					if ( $this->has_addons() &&
2975
					     'plugin-information' === fs_request_get( 'tab', false ) &&
2976
					     $this->get_id() == fs_request_get( 'parent_plugin_id', false )
2977
					) {
2978
						require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php';
2979
2980
						new FS_Plugin_Info_Dialog( $this );
2981
					}
2982
				}
2983
2984
				add_action( 'admin_init', array( &$this, '_admin_init_action' ) );
2985
2986
//				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...
2987
//				     $this->is_anonymous() ||
2988
//				     $this->is_pending_activation()
2989
//				) {
2990
//					$this->_init_admin();
2991
//				}
2992
			}
2993
2994
			/**
2995
			 * Should be called outside `$this->is_user_in_admin()` scope
2996
			 * because the updater has some logic that needs to be executed
2997
			 * during AJAX calls.
2998
			 *
2999
			 * Currently we need to hook to the `http_request_host_is_external` filter.
3000
			 * In the future, there might be additional logic added.
3001
			 *
3002
			 * @author Vova Feldman
3003
			 * @since  1.2.1.6
3004
			 */
3005
			if ( $this->is_premium() && $this->has_release_on_freemius() ) {
3006
				new FS_Plugin_Updater( $this );
3007
			}
3008
3009
			$this->do_action( 'initiated' );
3010
3011
			if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) {
3012
				if ( isset( $this->_storage->prev_is_premium ) ) {
3013
					$this->apply_filters(
3014
						'after_code_type_change',
3015
						// New code type.
3016
						$this->_plugin->is_premium
3017
					);
3018
				} else {
3019
					// Set for code type for the first time.
3020
					$this->_storage->prev_is_premium = $this->_plugin->is_premium;
3021
				}
3022
			}
3023
3024
			if ( ! $this->is_addon() ) {
3025
				if ( $this->is_registered() ) {
3026
					// Fix for upgrade from versions < 1.0.9.
3027
					if ( ! isset( $this->_storage->activation_timestamp ) ) {
3028
						$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
3029
					}
3030
3031
					$this->do_action( 'after_init_plugin_registered' );
3032
				} else if ( $this->is_anonymous() ) {
3033
					$this->do_action( 'after_init_plugin_anonymous' );
3034
				} else if ( $this->is_pending_activation() ) {
3035
					$this->do_action( 'after_init_plugin_pending_activations' );
3036
				}
3037
			} else {
3038
				if ( $this->is_registered() ) {
3039
					$this->do_action( 'after_init_addon_registered' );
3040
				} else if ( $this->is_anonymous() ) {
3041
					$this->do_action( 'after_init_addon_anonymous' );
3042
				} else if ( $this->is_pending_activation() ) {
3043
					$this->do_action( 'after_init_addon_pending_activations' );
3044
				}
3045
			}
3046
		}
3047
3048
		/**
3049
		 * @author Leo Fajardo (@leorw)
3050
		 *
3051
		 * @since  1.2.1.5
3052
		 */
3053
		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...
3054
			$this->_logger->entrance();
3055
3056
			$this->check_ajax_referer( 'stop_tracking' );
3057
3058
			$result = $this->stop_tracking();
3059
3060
			if ( true === $result ) {
3061
				self::shoot_ajax_success();
3062
			}
3063
3064
			$this->_logger->api_error( $result );
3065
3066
			self::shoot_ajax_failure(
3067
				$this->get_text( 'unexpected-api-error' ) .
3068
				( $this->is_api_error( $result ) && isset( $result->error ) ?
3069
					$result->error->message :
3070
					var_export( $result, true ) )
3071
			);
3072
		}
3073
3074
		/**
3075
		 * @author Leo Fajardo (@leorw)
3076
		 * @since  1.2.1.5
3077
		 */
3078
		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...
3079
			$this->_logger->entrance();
3080
3081
			$this->check_ajax_referer( 'allow_tracking' );
3082
3083
			$result = $this->allow_tracking();
3084
3085
			if ( true === $result ) {
3086
				self::shoot_ajax_success();
3087
			}
3088
3089
			$this->_logger->api_error( $result );
3090
3091
			self::shoot_ajax_failure(
3092
				$this->get_text( 'unexpected-api-error' ) .
3093
				( $this->is_api_error( $result ) && isset( $result->error ) ?
3094
					$result->error->message :
3095
					var_export( $result, true ) )
3096
			);
3097
		}
3098
3099
		/**
3100
		 * Opt-out from usage tracking.
3101
		 *
3102
		 * Note: This will not delete the account information but will stop all tracking.
3103
		 *
3104
		 * Returns:
3105
		 *  1. FALSE  - If the user never opted-in.
3106
		 *  2. TRUE   - If successfully opted-out.
3107
		 *  3. object - API result on failure.
3108
		 *
3109
		 * @author Leo Fajardo (@leorw)
3110
		 * @since  1.2.1.5
3111
		 *
3112
		 * @return bool|object
3113
		 */
3114
		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...
3115
			$this->_logger->entrance();
3116
3117
			if ( ! $this->is_registered() ) {
3118
				// User never opted-in.
3119
				return false;
3120
			}
3121
3122
			if ( $this->is_tracking_prohibited() ) {
3123
				// Already disconnected.
3124
				return true;
3125
			}
3126
3127
			// Send update to FS.
3128
			$result = $this->get_api_site_scope()->call( '/?fields=is_disconnected', 'put', array(
3129
				'is_disconnected' => true
3130
			) );
3131
3132
			if ( ! $this->is_api_result_entity( $result ) ||
3133
			     ! isset( $result->is_disconnected ) ||
3134
			     ! $result->is_disconnected
3135
			) {
3136
				$this->_logger->api_error( $result );
3137
3138
				return $result;
3139
			}
3140
3141
			$this->_site->is_disconnected = $result->is_disconnected;
3142
			$this->_store_site();
3143
3144
			$this->clear_sync_cron();
3145
3146
			// Successfully disconnected.
3147
			return true;
3148
		}
3149
3150
		/**
3151
		 * Opt-in back into usage tracking.
3152
		 *
3153
		 * Note: This will only work if the user opted-in previously.
3154
		 *
3155
		 * Returns:
3156
		 *  1. FALSE  - If the user never opted-in.
3157
		 *  2. TRUE   - If successfully opted-in back to usage tracking.
3158
		 *  3. object - API result on failure.
3159
		 *
3160
		 * @author Leo Fajardo (@leorw)
3161
		 * @since  1.2.1.5
3162
		 *
3163
		 * @return bool|object
3164
		 */
3165
		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...
3166
			$this->_logger->entrance();
3167
3168
			if ( ! $this->is_registered() ) {
3169
				// User never opted-in.
3170
				return false;
3171
			}
3172
3173
			if ( $this->is_tracking_allowed() ) {
3174
				// Tracking already allowed.
3175
				return true;
3176
			}
3177
3178
			$result = $this->get_api_site_scope()->call( '/?is_disconnected', 'put', array(
3179
				'is_disconnected' => false
3180
			) );
3181
3182
			if ( ! $this->is_api_result_entity( $result ) ||
3183
			     ! isset( $result->is_disconnected ) ||
3184
			     $result->is_disconnected
3185
			) {
3186
				$this->_logger->api_error( $result );
3187
3188
				return $result;
3189
			}
3190
3191
			$this->_site->is_disconnected = $result->is_disconnected;
3192
			$this->_store_site();
3193
3194
			$this->schedule_sync_cron();
3195
3196
			// Successfully reconnected.
3197
			return true;
3198
		}
3199
3200
		/**
3201
		 * If user opted-in and later disabled usage-tracking,
3202
		 * re-allow tracking for licensing and updates.
3203
		 *
3204
		 * @author Leo Fajardo (@leorw)
3205
		 *
3206
		 * @since  1.2.1.5
3207
		 */
3208
		private function reconnect_locally() {
3209
			$this->_logger->entrance();
3210
3211
			if ( $this->is_tracking_prohibited() &&
3212
			     $this->is_registered()
3213
			) {
3214
				$this->_site->is_disconnected = false;
3215
				$this->_store_site();
3216
			}
3217
		}
3218
3219
		/**
3220
		 * Parse plugin's settings (as defined by the plugin dev).
3221
		 *
3222
		 * @author Vova Feldman (@svovaf)
3223
		 * @since  1.1.7.3
3224
		 *
3225
		 * @param array $plugin_info
3226
		 *
3227
		 * @throws \Freemius_Exception
3228
		 */
3229
		private function parse_settings( &$plugin_info ) {
3230
			$this->_logger->entrance();
3231
3232
			$id          = $this->get_numeric_option( $plugin_info, 'id', false );
3233
			$public_key  = $this->get_option( $plugin_info, 'public_key', false );
3234
			$secret_key  = $this->get_option( $plugin_info, 'secret_key', null );
3235
			$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...
3236
			$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...
3237
3238
			/**
3239
			 * @author Vova Feldman (@svovaf)
3240
			 * @since  1.1.9 Try to pull secret key from external config.
3241
			 */
3242
			if ( is_null( $secret_key ) && defined( "WP_FS__{$this->_slug}_SECRET_KEY" ) ) {
3243
				$secret_key = constant( "WP_FS__{$this->_slug}_SECRET_KEY" );
3244
			}
3245
3246
			if ( isset( $plugin_info['parent'] ) ) {
3247
				$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...
3248
//				$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...
3249
//				$parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null );
3250
//				$parent_name = $this->get_option( $plugin_info['parent'], 'name', null );
3251
			}
3252
3253
			if ( false === $id ) {
3254
				throw new Freemius_Exception( array(
3255
					'error' => array(
3256
						'type'    => 'ParameterNotSet',
3257
						'message' => 'Plugin id parameter is not set.',
3258
						'code'    => 'plugin_id_not_set',
3259
						'http'    => 500,
3260
					)
3261
				) );
3262
			}
3263
			if ( false === $public_key ) {
3264
				throw new Freemius_Exception( array(
3265
					'error' => array(
3266
						'type'    => 'ParameterNotSet',
3267
						'message' => 'Plugin public_key parameter is not set.',
3268
						'code'    => 'plugin_public_key_not_set',
3269
						'http'    => 500,
3270
					)
3271
				) );
3272
			}
3273
3274
			$plugin = ( $this->_plugin instanceof FS_Plugin ) ?
3275
				$this->_plugin :
3276
				new FS_Plugin();
3277
3278
			$plugin->update( array(
3279
				'id'               => $id,
3280
				'public_key'       => $public_key,
3281
				'slug'             => $this->_slug,
3282
				'parent_plugin_id' => $parent_id,
3283
				'version'          => $this->get_plugin_version(),
3284
				'title'            => $this->get_plugin_name(),
3285
				'file'             => $this->_plugin_basename,
3286
				'is_premium'       => $this->get_bool_option( $plugin_info, 'is_premium', true ),
3287
				'is_live'          => $this->get_bool_option( $plugin_info, 'is_live', true ),
3288
				'type'             => $this->_module_type,
3289
//				'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...
3290
			) );
3291
3292
			if ( $plugin->is_updated() ) {
3293
				// Update plugin details.
3294
				$this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->store( $plugin );
0 ignored issues
show
Documentation Bug introduced by
It seems like \FS_Plugin_Manager::inst...ule_id)->store($plugin) can also be of type boolean. However, the property $_plugin is declared as type object<FS_Plugin>. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3295
			}
3296
			// Set the secret key after storing the plugin, we don't want to store the key in the storage.
3297
			$this->_plugin->secret_key = $secret_key;
3298
3299
			if ( ! isset( $plugin_info['menu'] ) ) {
3300
				$plugin_info['menu'] = array();
3301
3302
				if ( ! empty( $this->_storage->sdk_last_version ) &&
3303
				     version_compare( $this->_storage->sdk_last_version, '1.1.2', '<=' )
3304
				) {
3305
					// Backward compatibility to 1.1.2
3306
					$plugin_info['menu']['slug'] = isset( $plugin_info['menu_slug'] ) ?
3307
						$plugin_info['menu_slug'] :
3308
						$this->_slug;
3309
				}
3310
			}
3311
3312
			$this->_menu = FS_Admin_Menu_Manager::instance(
3313
				$this->_module_id,
3314
				$this->_module_type,
3315
				$this->get_unique_affix()
3316
			);
3317
3318
			$this->_menu->init( $plugin_info['menu'], $this->is_addon() );
3319
3320
			$this->_has_addons          = $this->get_bool_option( $plugin_info, 'has_addons', false );
3321
			$this->_has_paid_plans      = $this->get_bool_option( $plugin_info, 'has_paid_plans', true );
3322
			$this->_has_premium_version = $this->get_bool_option( $plugin_info, 'has_premium_version', $this->_has_paid_plans );
3323
			$this->_ignore_pending_mode = $this->get_bool_option( $plugin_info, 'ignore_pending_mode', false );
3324
			$this->_is_org_compliant    = $this->get_bool_option( $plugin_info, 'is_org_compliant', true );
3325
			$this->_is_premium_only     = $this->get_bool_option( $plugin_info, 'is_premium_only', false );
3326
			if ( $this->_is_premium_only ) {
3327
				// If premium only plugin, disable anonymous mode.
3328
				$this->_enable_anonymous = false;
3329
				$this->_anonymous_mode   = false;
3330
			} else {
3331
				$this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true );
3332
				$this->_anonymous_mode   = $this->get_bool_option( $plugin_info, 'anonymous_mode', false );
3333
			}
3334
			$this->_permissions = $this->get_option( $plugin_info, 'permissions', array() );
3335
3336
			if ( ! empty( $plugin_info['trial'] ) ) {
3337
				$this->_trial_days = $this->get_numeric_option(
3338
					$plugin_info['trial'],
3339
					'days',
3340
					// Default to 0 - trial without days specification.
3341
					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...
3342
				);
3343
3344
				$this->_is_trial_require_payment = $this->get_bool_option( $plugin_info['trial'], 'is_require_payment', false );
3345
			}
3346
		}
3347
3348
		/**
3349
		 * @param string[] $options
3350
		 * @param string   $key
3351
		 * @param mixed    $default
3352
		 *
3353
		 * @return bool
3354
		 */
3355
		private function get_option( &$options, $key, $default = false ) {
3356
			return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
3357
		}
3358
3359
		private function get_bool_option( &$options, $key, $default = false ) {
3360
			return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
3361
		}
3362
3363
		private function get_numeric_option( &$options, $key, $default = false ) {
3364
			return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default;
3365
		}
3366
3367
		/**
3368
		 * Gate keeper.
3369
		 *
3370
		 * @author Vova Feldman (@svovaf)
3371
		 * @since  1.1.7.3
3372
		 *
3373
		 * @return bool
3374
		 */
3375
		private function should_stop_execution() {
3376
			if ( empty( $this->_storage->was_plugin_loaded ) ) {
3377
				/**
3378
				 * Don't execute Freemius until plugin was fully loaded at least once,
3379
				 * to give the opportunity for the activation hook to run before pinging
3380
				 * the API for connectivity test. This logic is relevant for the
3381
				 * identification of new plugin install vs. plugin update.
3382
				 *
3383
				 * @author Vova Feldman (@svovaf)
3384
				 * @since  1.1.9
3385
				 */
3386
				return true;
3387
			}
3388
3389
			if ( $this->is_activation_mode() ) {
3390
				if ( ! is_admin() ) {
3391
					/**
3392
					 * If in activation mode, don't execute Freemius outside of the
3393
					 * admin dashboard.
3394
					 *
3395
					 * @author Vova Feldman (@svovaf)
3396
					 * @since  1.1.7.3
3397
					 */
3398
					return true;
3399
				}
3400
3401
				if ( ! WP_FS__IS_HTTP_REQUEST ) {
3402
					/**
3403
					 * If in activation and executed without HTTP context (e.g. CLI, Cronjob),
3404
					 * then don't start Freemius.
3405
					 *
3406
					 * @author Vova Feldman (@svovaf)
3407
					 * @since  1.1.6.3
3408
					 *
3409
					 * @link   https://wordpress.org/support/topic/errors-in-the-freemius-class-when-running-in-wordpress-in-cli
3410
					 */
3411
					return true;
3412
				}
3413
3414
				if ( self::is_cron() ) {
3415
					/**
3416
					 * If in activation mode, don't execute Freemius during wp crons
3417
					 * (wp crons have HTTP context - called as HTTP request).
3418
					 *
3419
					 * @author Vova Feldman (@svovaf)
3420
					 * @since  1.1.7.3
3421
					 */
3422
					return true;
3423
				}
3424
3425
				if ( self::is_ajax() &&
3426
				     ! $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) &&
3427
				     ! $this->_admin_notices->has_sticky( 'failed_connect_api' )
3428
				) {
3429
					/**
3430
					 * During activation, if running in AJAX mode, unless there's a sticky
3431
					 * connectivity issue notice, don't run Freemius.
3432
					 *
3433
					 * @author Vova Feldman (@svovaf)
3434
					 * @since  1.1.7.3
3435
					 */
3436
					return true;
3437
				}
3438
			}
3439
3440
			return false;
3441
		}
3442
3443
		/**
3444
		 * Triggered after code type has changed.
3445
		 *
3446
		 * @author Vova Feldman (@svovaf)
3447
		 * @since  1.1.9.1
3448
		 */
3449
		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...
3450
			$this->_logger->entrance();
3451
3452
			if ( $this->is_theme() ) {
3453
				// Expire the cache of the previous tabs since the theme may
3454
				// have setting updates after code type has changed.
3455
				$this->_cache->expire( 'tabs' );
3456
				$this->_cache->expire( 'tabs_stylesheets' );
3457
			}
3458
3459
			if ( $this->is_registered() ) {
3460
				if ( ! $this->is_addon() ) {
3461
					add_action(
3462
						is_admin() ? 'admin_init' : 'init',
3463
						array( &$this, '_plugin_code_type_changed' )
3464
					);
3465
				}
3466
3467
				if ( $this->is_premium() ) {
3468
					// Purge cached payments after switching to the premium version.
3469
					// @todo This logic doesn't handle purging the cache for serviceware module upgrade.
3470
					$this->get_api_user_scope()->purge_cache( "/plugins/{$this->_module_id}/payments.json?include_addons=true" );
3471
				}
3472
			}
3473
		}
3474
3475
		/**
3476
		 * Handles plugin's code type change (free <--> premium).
3477
		 *
3478
		 * @author Vova Feldman (@svovaf)
3479
		 * @since  1.0.9
3480
		 */
3481
		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...
3482
			$this->_logger->entrance();
3483
3484
			if ( $this->is_premium() ) {
3485
				$this->reconnect_locally();
3486
3487
				// Activated premium code.
3488
				$this->do_action( 'after_premium_version_activation' );
3489
3490
				// Remove all sticky messages related to download of the premium version.
3491
				$this->_admin_notices->remove_sticky( array(
3492
					'trial_started',
3493
					'plan_upgraded',
3494
					'plan_changed',
3495
					'license_activated',
3496
				) );
3497
3498
				$this->_admin_notices->add_sticky(
3499
					sprintf( $this->get_text( 'premium-activated-message' ), $this->_module_type ),
3500
					'premium_activated',
3501
					$this->get_text( 'woot' ) . '!'
3502
				);
3503
			} else {
3504
				// Remove sticky message related to premium code activation.
3505
				$this->_admin_notices->remove_sticky( 'premium_activated' );
3506
3507
				// Activated free code (after had the premium before).
3508
				$this->do_action( 'after_free_version_reactivation' );
3509
3510
				if ( $this->is_paying() && ! $this->is_premium() ) {
3511
					$this->_admin_notices->add_sticky(
3512
						sprintf(
3513
							$this->get_text( 'you-have-x-license' ),
3514
							$this->_site->plan->title
3515
						) . $this->get_complete_upgrade_instructions(),
3516
						'plan_upgraded',
3517
						$this->get_text( 'yee-haw' ) . '!'
3518
					);
3519
				}
3520
			}
3521
3522
			// Schedule code type changes event.
3523
			$this->schedule_install_sync();
3524
3525
			/**
3526
			 * Unregister the uninstall hook for the other version of the plugin (with different code type) to avoid
3527
			 * triggering a fatal error when uninstalling that plugin. For example, after deactivating the "free" version
3528
			 * of a specific plugin, its uninstall hook should be unregistered after the "premium" version has been
3529
			 * activated. If we don't do that, a fatal error will occur when we try to uninstall the "free" version since
3530
			 * the main file of the "free" version will be loaded first before calling the hooked callback. Since the
3531
			 * free and premium versions are almost identical (same class or have same functions), a fatal error like
3532
			 * "Cannot redeclare class MyClass" or "Cannot redeclare my_function()" will occur.
3533
			 */
3534
			$this->unregister_uninstall_hook();
3535
3536
			$this->clear_module_main_file_cache();
3537
3538
			// Update is_premium of latest version.
3539
			$this->_storage->prev_is_premium = $this->_plugin->is_premium;
3540
		}
3541
3542
		#endregion
3543
3544
		#----------------------------------------------------------------------------------
3545
		#region Add-ons
3546
		#----------------------------------------------------------------------------------
3547
3548
		/**
3549
		 * Check if add-on installed and activated on site.
3550
		 *
3551
		 * @author Vova Feldman (@svovaf)
3552
		 * @since  1.0.6
3553
		 *
3554
		 * @param string|number $id_or_slug
3555
		 * @param bool|null     $is_premium Since 1.2.1.7 can check for specified add-on version.
3556
		 *
3557
		 * @return bool
3558
		 */
3559
		function is_addon_activated( $id_or_slug, $is_premium = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3560
			$this->_logger->entrance();
3561
3562
			$addon_id     = self::get_module_id( $id_or_slug );
3563
			$is_activated = self::has_instance( $addon_id );
3564
3565
			if ( ! $is_activated ) {
3566
				return false;
3567
			}
3568
3569
			if ( is_bool( $is_premium ) ) {
3570
				// Check if the specified code version is activate.
3571
				$addon        = $this->get_addon_instance( $addon_id );
3572
				$is_activated = ( $is_premium === $addon->is_premium() );
3573
			}
3574
3575
			return $is_activated;
3576
		}
3577
3578
		/**
3579
		 * Check if add-on was connected to install
3580
		 *
3581
		 * @author Vova Feldman (@svovaf)
3582
		 * @since  1.1.7
3583
		 *
3584
		 * @param  string|number $id_or_slug
3585
		 *
3586
		 * @return bool
3587
		 */
3588
		function is_addon_connected( $id_or_slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3589
			$this->_logger->entrance();
3590
3591
			$sites = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN );
3592
3593
			$addon_id = self::get_module_id( $id_or_slug );
3594
			$addon    = $this->get_addon( $addon_id );
3595
			$slug     = $addon->slug;
3596
			if ( ! isset( $sites[ $slug ] ) ) {
3597
				return false;
3598
			}
3599
3600
			$site = $sites[ $slug ];
3601
3602
			$plugin = FS_Plugin_Manager::instance( $addon_id )->get();
3603
3604
			if ( $plugin->parent_plugin_id != $this->_plugin->id ) {
3605
				// The given slug do NOT belong to any of the plugin's add-ons.
3606
				return false;
3607
			}
3608
3609
			return ( is_object( $site ) &&
3610
			         is_numeric( $site->id ) &&
3611
			         is_numeric( $site->user_id ) &&
3612
			         is_object( $site->plan )
3613
			);
3614
		}
3615
3616
		/**
3617
		 * Determines if add-on installed.
3618
		 *
3619
		 * NOTE: This is a heuristic and only works if the folder/file named as the slug.
3620
		 *
3621
		 * @author Vova Feldman (@svovaf)
3622
		 * @since  1.0.6
3623
		 *
3624
		 * @param  string|number $id_or_slug
3625
		 *
3626
		 * @return bool
3627
		 */
3628
		function is_addon_installed( $id_or_slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3629
			$this->_logger->entrance();
3630
3631
			$addon_id = self::get_module_id( $id_or_slug );
3632
3633
			return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $addon_id ) ) );
3634
		}
3635
3636
		/**
3637
		 * Get add-on basename.
3638
		 *
3639
		 * @author Vova Feldman (@svovaf)
3640
		 * @since  1.0.6
3641
		 *
3642
		 * @param  string|number $id_or_slug
3643
		 *
3644
		 * @return string
3645
		 */
3646
		function get_addon_basename( $id_or_slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3647
			$addon_id = self::get_module_id( $id_or_slug );
3648
3649
			if ( $this->is_addon_activated( $addon_id ) ) {
3650
				return self::instance( $addon_id )->get_plugin_basename();
3651
			}
3652
3653
			$addon            = $this->get_addon( $addon_id );
3654
			$premium_basename = "{$addon->slug}-premium/{$addon->slug}.php";
3655
3656
			if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_basename ) ) ) {
3657
				return $premium_basename;
3658
			}
3659
3660
			$all_plugins = $this->get_all_plugins();
3661
3662
			foreach ( $all_plugins as $basename => &$data ) {
3663
				if ( $addon->slug === $data['slug'] ||
3664
                    $addon->slug . '-premium' === $data['slug']
3665
				) {
3666
					return $basename;
3667
				}
3668
			}
3669
3670
			$free_basename = "{$addon->slug}/{$addon->slug}.php";
3671
3672
			return $free_basename;
3673
		}
3674
3675
		/**
3676
		 * Get installed add-ons instances.
3677
		 *
3678
		 * @author Vova Feldman (@svovaf)
3679
		 * @since  1.0.6
3680
		 *
3681
		 * @return Freemius[]
3682
		 */
3683
		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...
3684
			$installed_addons = array();
3685
			foreach ( self::$_instances as $instance ) {
3686
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
3687
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
3688
						$installed_addons[] = $instance;
3689
					}
3690
				}
3691
			}
3692
3693
			return $installed_addons;
3694
		}
3695
3696
		/**
3697
		 * Check if any add-ons of the plugin are installed.
3698
		 *
3699
		 * @author Leo Fajardo (@leorw)
3700
		 * @since  1.1.1
3701
		 *
3702
		 * @return bool
3703
		 */
3704
		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...
3705
			if ( ! $this->has_addons() ) {
3706
				return false;
3707
			}
3708
3709
			foreach ( self::$_instances as $instance ) {
3710
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
3711
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
3712
						return true;
3713
					}
3714
				}
3715
			}
3716
3717
			return false;
3718
		}
3719
3720
		/**
3721
		 * Tell Freemius that the current plugin is an add-on.
3722
		 *
3723
		 * @author Vova Feldman (@svovaf)
3724
		 * @since  1.0.6
3725
		 *
3726
		 * @param number $parent_plugin_id The parent plugin ID
3727
		 */
3728
		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...
3729
			$this->_plugin->parent_plugin_id = $parent_plugin_id;
3730
		}
3731
3732
		/**
3733
		 * @author Vova Feldman (@svovaf)
3734
		 * @since  1.0.6
3735
		 *
3736
		 * @return bool
3737
		 */
3738
		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...
3739
			return isset( $this->_plugin->parent_plugin_id ) && is_numeric( $this->_plugin->parent_plugin_id );
3740
		}
3741
3742
		/**
3743
		 * Deactivate add-on if it's premium only and the user does't have a valid license.
3744
		 *
3745
		 * @param bool $is_after_trial_cancel
3746
		 *
3747
		 * @return bool If add-on was deactivated.
3748
		 */
3749
		private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) {
3750
			if ( ! $this->has_free_plan() &&
3751
			     ! $this->has_features_enabled_license() &&
3752
			     ! $this->_has_premium_license()
3753
			) {
3754
				if ( $this->is_registered() ) {				
3755
					// IF wrapper is turned off because activation_timestamp is currently only stored for plugins (not addons).
3756
	//                if (empty($this->_storage->activation_timestamp) ||
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

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

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

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

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

Loading history...
3758
	//                ) {
3759
					/**
3760
					 * @todo When it's first fail, there's no reason to try and re-sync because the licenses were just synced after initial activation.
3761
					 *
3762
					 * Retry syncing the user add-on licenses.
3763
					 */
3764
					// Sync licenses.
3765
					$this->_sync_licenses();
3766
	//                }
3767
3768
					// Try to activate premium license.
3769
					$this->_activate_license( true );
3770
				}
3771
3772
				if ( ! $this->has_free_plan() &&
3773
				     ! $this->has_features_enabled_license() &&
3774
				     ! $this->_has_premium_license()
3775
				) {
3776
					// @todo Check if deactivate plugins also call the deactivation hook.
3777
3778
					$this->_parent->_admin_notices->add_sticky(
3779
						sprintf(
3780
							$this->_parent->get_text( $is_after_trial_cancel ?
3781
								'addon-trial-cancelled-message' :
3782
								'addon-no-license-message'
3783
							),
3784
							'<b>' . $this->_plugin->title . '</b>'
3785
						) . ' ' . sprintf(
3786
							'<a href="%s" aria-label="%s" class="button button-primary" style="margin-left: 10px; vertical-align: middle;">%s &nbsp;&#10140;</a>',
3787
							$this->_parent->addon_url( $this->_slug ),
3788
							esc_attr( sprintf( $this->_parent->get_text( 'more-information-about-x' ), $this->_plugin->title ) ),
3789
							$this->_parent->get_text( 'purchase-license' )
3790
						),
3791
						'no_addon_license_' . $this->_slug,
3792
						( $is_after_trial_cancel ? '' : $this->_parent->get_text( 'oops' ) . '...' ),
3793
						( $is_after_trial_cancel ? 'success' : 'error' )
3794
					);
3795
3796
					deactivate_plugins( array( $this->_plugin_basename ), true );
3797
3798
					return true;
3799
				}
3800
			}
3801
3802
			return false;
3803
		}
3804
3805
		#endregion
3806
3807
		#----------------------------------------------------------------------------------
3808
		#region Sandbox
3809
		#----------------------------------------------------------------------------------
3810
3811
		/**
3812
		 * Set Freemius into sandbox mode for debugging.
3813
		 *
3814
		 * @author Vova Feldman (@svovaf)
3815
		 * @since  1.0.4
3816
		 *
3817
		 * @param string $secret_key
3818
		 */
3819
		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...
3820
			$this->_plugin->secret_key = $secret_key;
3821
3822
			// Update plugin details.
3823
			FS_Plugin_Manager::instance( $this->_module_id )->update( $this->_plugin, true );
3824
		}
3825
3826
		/**
3827
		 * Check if running payments in sandbox mode.
3828
		 *
3829
		 * @author Vova Feldman (@svovaf)
3830
		 * @since  1.0.4
3831
		 *
3832
		 * @return bool
3833
		 */
3834
		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...
3835
			return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key );
3836
		}
3837
3838
		#endregion
3839
3840
		/**
3841
		 * Check if running test vs. live plugin.
3842
		 *
3843
		 * @author Vova Feldman (@svovaf)
3844
		 * @since  1.0.5
3845
		 *
3846
		 * @return bool
3847
		 */
3848
		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...
3849
			return $this->_plugin->is_live;
3850
		}
3851
3852
		/**
3853
		 * Check if the user skipped connecting the account with Freemius.
3854
		 *
3855
		 * @author Vova Feldman (@svovaf)
3856
		 * @since  1.0.7
3857
		 *
3858
		 * @return bool
3859
		 */
3860
		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...
3861
			if ( ! isset( $this->_is_anonymous ) ) {
3862
				if ( ! isset( $this->_storage->is_anonymous ) ) {
3863
					// Not skipped.
3864
					$this->_is_anonymous = false;
3865
				} else if ( is_bool( $this->_storage->is_anonymous ) ) {
3866
					// For back compatibility, since the variable was boolean before.
3867
					$this->_is_anonymous = $this->_storage->is_anonymous;
3868
3869
					// Upgrade stored data format to 1.1.3 format.
3870
					$this->set_anonymous_mode( $this->_storage->is_anonymous );
3871
				} else {
3872
					// Version 1.1.3 and later.
3873
					$this->_is_anonymous = $this->_storage->is_anonymous['is'];
3874
				}
3875
			}
3876
3877
			return $this->_is_anonymous;
3878
		}
3879
3880
		/**
3881
		 * Check if user connected his account and install pending email activation.
3882
		 *
3883
		 * @author Vova Feldman (@svovaf)
3884
		 * @since  1.0.7
3885
		 *
3886
		 * @return bool
3887
		 */
3888
		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...
3889
			return $this->_storage->get( 'is_pending_activation', false );
3890
		}
3891
3892
		/**
3893
		 * Check if plugin must be WordPress.org compliant.
3894
		 *
3895
		 * @since 1.0.7
3896
		 *
3897
		 * @return bool
3898
		 */
3899
		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...
3900
			return $this->_is_org_compliant;
3901
		}
3902
3903
		#----------------------------------------------------------------------------------
3904
		#region Daily Sync Cron
3905
		#----------------------------------------------------------------------------------
3906
3907
		/**
3908
		 * @author Vova Feldman (@svovaf)
3909
		 * @since  1.1.7.3
3910
		 */
3911
		private function run_manual_sync() {
3912
			self::require_pluggable_essentials();
3913
3914
			if ( ! $this->is_user_admin() ) {
3915
				return;
3916
			}
3917
3918
			// Run manual sync.
3919
			$this->_sync_cron();
3920
3921
			// Reschedule next cron to run 24 hours from now (performance optimization).
3922
			$this->clear_sync_cron();
3923
3924
			$this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false );
3925
		}
3926
3927
		/**
3928
		 * Data sync cron job. Replaces the background sync non blocking HTTP request
3929
		 * that doesn't halt page loading.
3930
		 *
3931
		 * @author Vova Feldman (@svovaf)
3932
		 * @since  1.1.7.3
3933
		 */
3934
		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...
3935
			$this->_logger->entrance();
3936
3937
			// Store the last time data sync was executed.
3938
			$this->_storage->sync_timestamp = time();
3939
3940
			// Check if API is temporary down.
3941
			if ( FS_Api::is_temporary_down() ) {
3942
				return;
3943
			}
3944
3945
			// @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours.
3946
3947
			if ( $this->is_registered() ) {
3948
				if ( $this->has_paid_plan() ) {
3949
					// Initiate background plan sync.
3950
					$this->_sync_license( true );
3951
3952
					if ( $this->is_paying() ) {
3953
						// Check for premium plugin updates.
3954
						$this->check_updates( true );
3955
					}
3956
				} else {
3957
					// Sync install (only if something changed locally).
3958
					$this->sync_install();
3959
				}
3960
			}
3961
3962
			$this->do_action( 'after_sync_cron' );
3963
		}
3964
3965
		/**
3966
		 * Check if sync was executed in the last $period of seconds.
3967
		 *
3968
		 * @author Vova Feldman (@svovaf)
3969
		 * @since  1.1.7.3
3970
		 *
3971
		 * @param int $period In seconds
3972
		 *
3973
		 * @return bool
3974
		 */
3975
		private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) {
3976
			if ( ! isset( $this->_storage->sync_timestamp ) ) {
3977
				return false;
3978
			}
3979
3980
			return ( $this->_storage->sync_timestamp > ( WP_FS__SCRIPT_START_TIME - $period ) );
3981
		}
3982
3983
		/**
3984
		 * @author Vova Feldman (@svovaf)
3985
		 * @since  1.1.7.3
3986
		 *
3987
		 * @return bool
3988
		 */
3989
		private function is_sync_cron_on() {
3990
			/**
3991
			 * @var object $sync_cron_data
3992
			 */
3993
			$sync_cron_data = $this->_storage->get( 'sync_cron', null );
3994
3995
			return ( ! is_null( $sync_cron_data ) && true === $sync_cron_data->on );
3996
		}
3997
3998
		/**
3999
		 * @author Vova Feldman (@svovaf)
4000
		 * @since  1.1.7.3
4001
		 *
4002
		 * @param int  $start_at        Defaults to now.
4003
		 * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise,
4004
		 *                              schedule job to start right away.
4005
		 */
4006
		private function schedule_sync_cron( $start_at = WP_FS__SCRIPT_START_TIME, $randomize_start = true ) {
4007
			$this->_logger->entrance();
4008
4009
			if ( $randomize_start ) {
4010
				// Schedule first sync with a random 12 hour time range from now.
4011
				$start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) );
4012
			}
4013
4014
			// Schedule daily WP cron.
4015
			wp_schedule_event(
4016
				$start_at,
4017
				'daily',
4018
				$this->get_action_tag( 'data_sync' )
4019
			);
4020
4021
			$this->_storage->store( 'sync_cron', (object) array(
4022
				'version'     => $this->get_plugin_version(),
4023
				'sdk_version' => $this->version,
4024
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
4025
				'on'          => true,
4026
			) );
4027
		}
4028
4029
		/**
4030
		 * Add the actual sync function to the cron job hook.
4031
		 *
4032
		 * @author Vova Feldman (@svovaf)
4033
		 * @since  1.1.7.3
4034
		 */
4035
		private function hook_callback_to_sync_cron() {
4036
			$this->add_action( 'data_sync', array( &$this, '_sync_cron' ) );
4037
		}
4038
4039
		/**
4040
		 * @author Vova Feldman (@svovaf)
4041
		 * @since  1.1.7.3
4042
		 */
4043
		private function clear_sync_cron() {
4044
			$this->_logger->entrance();
4045
4046
			if ( ! $this->is_sync_cron_on() ) {
4047
				return;
4048
			}
4049
4050
			$this->_storage->remove( 'sync_cron' );
4051
4052
			wp_clear_scheduled_hook( $this->get_action_tag( 'data_sync' ) );
4053
		}
4054
4055
		/**
4056
		 * Unix timestamp for next sync cron execution or false if not scheduled.
4057
		 *
4058
		 * @author Vova Feldman (@svovaf)
4059
		 * @since  1.1.7.3
4060
		 *
4061
		 * @return int|false
4062
		 */
4063
		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...
4064
			$this->_logger->entrance();
4065
4066
			if ( ! $this->is_sync_cron_on() ) {
4067
				return false;
4068
			}
4069
4070
			return wp_next_scheduled( $this->get_action_tag( 'data_sync' ) );
4071
		}
4072
4073
		/**
4074
		 * Unix timestamp for previous sync cron execution or false if never executed.
4075
		 *
4076
		 * @author Vova Feldman (@svovaf)
4077
		 * @since  1.1.7.3
4078
		 *
4079
		 * @return int|false
4080
		 */
4081
		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...
4082
			$this->_logger->entrance();
4083
4084
			return $this->_storage->get( 'sync_timestamp' );
4085
		}
4086
4087
		#endregion Daily Sync Cron ------------------------------------------------------------------
4088
4089
		#----------------------------------------------------------------------------------
4090
		#region Async Install Sync
4091
		#----------------------------------------------------------------------------------
4092
4093
		/**
4094
		 * @author Vova Feldman (@svovaf)
4095
		 * @since  1.1.7.3
4096
		 *
4097
		 * @return bool
4098
		 */
4099
		private function is_install_sync_scheduled() {
4100
			/**
4101
			 * @var object $cron_data
4102
			 */
4103
			$cron_data = $this->_storage->get( 'install_sync_cron', null );
4104
4105
			return ( ! is_null( $cron_data ) && true === $cron_data->on );
4106
		}
4107
4108
		/**
4109
		 * Instead of running blocking install sync event, execute non blocking scheduled wp-cron.
4110
		 *
4111
		 * @author Vova Feldman (@svovaf)
4112
		 * @since  1.1.7.3
4113
		 */
4114
		private function schedule_install_sync() {
4115
			$this->_logger->entrance();
4116
4117
			$this->clear_install_sync_cron();
4118
4119
			// Schedule immediate install sync.
4120
			wp_schedule_single_event(
4121
				WP_FS__SCRIPT_START_TIME,
4122
				$this->get_action_tag( 'install_sync' )
4123
			);
4124
4125
			$this->_storage->store( 'install_sync_cron', (object) array(
4126
				'version'     => $this->get_plugin_version(),
4127
				'sdk_version' => $this->version,
4128
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
4129
				'on'          => true,
4130
			) );
4131
		}
4132
4133
		/**
4134
		 * Unix timestamp for previous install sync cron execution or false if never executed.
4135
		 *
4136
		 * @todo   There's some very strange bug that $this->_storage->install_sync_timestamp value is not being
4137
		 *         updated. But for sure the sync event is working.
4138
		 *
4139
		 * @author Vova Feldman (@svovaf)
4140
		 * @since  1.1.7.3
4141
		 *
4142
		 * @return int|false
4143
		 */
4144
		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...
4145
			$this->_logger->entrance();
4146
4147
			return $this->_storage->get( 'install_sync_timestamp' );
4148
		}
4149
4150
		/**
4151
		 * Unix timestamp for next install sync cron execution or false if not scheduled.
4152
		 *
4153
		 * @author Vova Feldman (@svovaf)
4154
		 * @since  1.1.7.3
4155
		 *
4156
		 * @return int|false
4157
		 */
4158
		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...
4159
			$this->_logger->entrance();
4160
4161
			if ( ! $this->is_install_sync_scheduled() ) {
4162
				return false;
4163
			}
4164
4165
			return wp_next_scheduled( $this->get_action_tag( 'install_sync' ) );
4166
		}
4167
4168
		/**
4169
		 * Add the actual install sync function to the cron job hook.
4170
		 *
4171
		 * @author Vova Feldman (@svovaf)
4172
		 * @since  1.1.7.3
4173
		 */
4174
		private function hook_callback_to_install_sync() {
4175
			$this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) );
4176
		}
4177
4178
		/**
4179
		 * @author Vova Feldman (@svovaf)
4180
		 * @since  1.1.7.3
4181
		 */
4182
		private function clear_install_sync_cron() {
4183
			$this->_logger->entrance();
4184
4185
			if ( ! $this->is_install_sync_scheduled() ) {
4186
				return;
4187
			}
4188
4189
			$this->_storage->remove( 'install_sync_cron' );
4190
4191
			wp_clear_scheduled_hook( $this->get_action_tag( 'install_sync' ) );
4192
		}
4193
4194
		/**
4195
		 * @author Vova Feldman (@svovaf)
4196
		 * @since  1.1.7.3
4197
		 */
4198
		public function _run_sync_install() {
4199
			$this->_logger->entrance();
4200
4201
			// Update last install sync timestamp.
4202
			$this->_storage->install_sync_timestamp = time();
4203
4204
			$this->sync_install( array(), true );
4205
		}
4206
4207
		#endregion Async Install Sync ------------------------------------------------------------------
4208
4209
		/**
4210
		 * Show a notice that activation is currently pending.
4211
		 *
4212
		 * @author Vova Feldman (@svovaf)
4213
		 * @since  1.0.7
4214
		 *
4215
		 * @param bool|string $email
4216
		 * @param bool        $is_pending_trial Since 1.2.1.5
4217
		 */
4218
		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...
4219
			if ( ! is_string( $email ) ) {
4220
				$current_user = self::_get_current_wp_user();
4221
				$email        = $current_user->user_email;
4222
			}
4223
4224
			$this->_admin_notices->add_sticky(
4225
				sprintf(
4226
					$this->get_text( 'pending-activation-message' ),
4227
					'<b>' . $this->get_plugin_name() . '</b>',
4228
					'<b>' . $email . '</b>',
4229
					$this->get_text( $is_pending_trial ? 'start-the-trial' : 'complete-the-install' )
4230
				),
4231
				'activation_pending',
4232
				'Thanks!'
4233
			);
4234
		}
4235
4236
		/**
4237
		 * Check if currently in plugin activation.
4238
		 *
4239
		 * @author Vova Feldman (@svovaf)
4240
		 * @since  1.1.4
4241
		 *
4242
		 * @return bool
4243
		 */
4244
		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...
4245
			return get_option( 'fs_'
4246
			                   . ( $this->is_plugin() ? '' : $this->_module_type . '_' )
4247
			                   . "{$this->_slug}_activated", false );
4248
		}
4249
4250
		/**
4251
		 *
4252
		 * NOTE: admin_menu action executed before admin_init.
4253
		 *
4254
		 * @author Vova Feldman (@svovaf)
4255
		 * @since  1.0.7
4256
		 */
4257
		function _admin_init_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

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

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
4258
			/**
4259
			 * Automatically redirect to connect/activation page after plugin activation.
4260
			 *
4261
			 * @since 1.1.7 Do NOT redirect to opt-in when running in network admin mode.
4262
			 */
4263
			if ( $this->is_plugin_activation() ) {
4264
				delete_option( 'fs_'
4265
				               . ( $this->is_plugin() ? '' : $this->_module_type . '_' )
4266
				               . "{$this->_slug}_activated" );
4267
4268
				if ( ! function_exists( 'is_network_admin' ) || ! is_network_admin() ) {
4269
					$this->_redirect_on_activation_hook();
4270
4271
					return;
4272
				}
4273
			}
4274
4275
			if ( fs_request_is_action( $this->get_unique_affix() . '_skip_activation' ) ) {
4276
				check_admin_referer( $this->get_unique_affix() . '_skip_activation' );
4277
4278
				$this->skip_connection();
4279
4280
				fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) );
4281
			}
4282
4283
			if ( ! $this->is_addon() && ! $this->is_registered() && ! $this->is_anonymous() ) {
4284
				if ( ! $this->is_pending_activation() ) {
4285
					if ( ! $this->_menu->is_main_settings_page() ) {
4286
						/**
4287
						 * If a user visits any other admin page before activating the premium-only theme with a valid
4288
						 * license, reactivate the previous theme.
4289
						 *
4290
						 * @author Leo Fajardo (@leorw)
4291
						 * @since  1.2.2
4292
						 */
4293
						if ( $this->is_theme()
4294
						     && $this->is_only_premium()
4295
						     && ! $this->has_settings_menu()
4296
						     && ! isset( $_REQUEST['fs_action'] )
4297
						     && $this->can_activate_previous_theme()
4298
						) {
4299
							$this->activate_previous_theme();
4300
4301
							return;
4302
						}
4303
4304
						if ( $this->is_plugin_new_install() || $this->is_only_premium() ) {
4305
							// Show notice for new plugin installations.
4306
							$this->_admin_notices->add(
4307
								sprintf(
4308
									$this->get_text( 'you-are-step-away' ),
4309
									sprintf( '<b><a href="%s">%s</a></b>',
4310
										$this->get_activation_url(),
4311
										sprintf( $this->get_text( 'activate-x-now' ), $this->get_plugin_name() )
4312
									)
4313
								),
4314
								'',
4315
								'update-nag'
4316
							);
4317
						} else {
4318
							if ( ! isset( $this->_storage->sticky_optin_added ) ) {
4319
								$this->_storage->sticky_optin_added = true;
4320
4321
								// Show notice for new plugin installations.
4322
								$this->_admin_notices->add_sticky(
4323
									sprintf(
4324
										$this->get_text( 'few-plugin-tweaks' ),
4325
										$this->_module_type,
4326
										sprintf( '<b><a href="%s">%s</a></b>',
4327
											$this->get_activation_url(),
4328
											sprintf( $this->get_text( 'optin-x-now' ), $this->get_plugin_name() )
4329
										)
4330
									),
4331
									'connect_account',
4332
									'',
4333
									'update-nag'
4334
								);
4335
							}
4336
4337
							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...
4338
								// Don't show admin nag if plugin update.
4339
								wp_enqueue_script( 'wp-pointer' );
4340
								wp_enqueue_style( 'wp-pointer' );
4341
4342
								$this->_enqueue_connect_essentials();
4343
4344
								add_action( 'admin_print_footer_scripts', array(
4345
									$this,
4346
									'_add_connect_pointer_script'
4347
								) );
4348
							}
4349
						}
4350
					}
4351
				}
4352
4353
				if ( $this->is_theme() &&
4354
				     $this->_menu->is_main_settings_page()
4355
				) {
4356
					$this->_show_theme_activation_optin_dialog();
4357
				}
4358
			}
4359
4360
			$this->_add_upgrade_action_link();
4361
		}
4362
4363
		/**
4364
		 * Enqueue connect requires scripts and styles.
4365
		 *
4366
		 * @author Vova Feldman (@svovaf)
4367
		 * @since  1.1.4
4368
		 */
4369
		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...
4370
			wp_enqueue_script( 'jquery' );
4371
			wp_enqueue_script( 'json2' );
4372
4373
			fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' );
4374
			fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' );
4375
4376
			fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' );
4377
		}
4378
4379
		/**
4380
		 * Add connect / opt-in pointer.
4381
		 *
4382
		 * @author Vova Feldman (@svovaf)
4383
		 * @since  1.1.4
4384
		 */
4385
		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...
4386
			$vars            = array( 'id' => $this->_module_id );
4387
			$pointer_content = fs_get_template( 'connect.php', $vars );
4388
			?>
4389
			<script type="text/javascript">// <![CDATA[
4390
				jQuery(document).ready(function ($) {
4391
					if ('undefined' !== typeof(jQuery().pointer)) {
4392
4393
						var element = <?php echo $this->apply_filters( 'optin_pointer_element', '$("#non_existing_element");' ) ?>;
4394
4395
						if (element.length > 0) {
4396
							var optin = $(element).pointer($.extend(true, {}, {
4397
								content     : <?php echo json_encode( $pointer_content ) ?>,
4398
								position    : {
4399
									edge : 'left',
4400
									align: 'center'
4401
								},
4402
								buttons     : function () {
4403
									// Don't show pointer buttons.
4404
									return '';
4405
								},
4406
								pointerWidth: 482
4407
							}, <?php echo $this->apply_filters( 'optin_pointer_options_json', '{}' ) ?>));
4408
4409
							<?php
4410
							echo $this->apply_filters( 'optin_pointer_execute', "
4411
4412
							optin.pointer('open');
4413
4414
							// Tag the opt-in pointer with custom class.
4415
							$('.wp-pointer #fs_connect')
4416
								.parents('.wp-pointer.wp-pointer-top')
4417
								.addClass('fs-opt-in-pointer');
4418
4419
							", 'element', 'optin' ) ?>
4420
						}
4421
					}
4422
				});
4423
				// ]]></script>
4424
			<?php
4425
		}
4426
4427
		/**
4428
		 * Return current page's URL.
4429
		 *
4430
		 * @author Vova Feldman (@svovaf)
4431
		 * @since  1.0.7
4432
		 *
4433
		 * @return string
4434
		 */
4435
		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...
4436
			$url = 'http';
4437
4438
			if ( isset( $_SERVER["HTTPS"] ) ) {
4439
				if ( $_SERVER["HTTPS"] == "on" ) {
4440
					$url .= "s";
4441
				}
4442
			}
4443
			$url .= "://";
4444
			if ( $_SERVER["SERVER_PORT"] != "80" ) {
4445
				$url .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
4446
			} else {
4447
				$url .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
4448
			}
4449
4450
			return esc_url( $url );
4451
		}
4452
4453
		/**
4454
		 * Check if the current page is the plugin's main admin settings page.
4455
		 *
4456
		 * @author Vova Feldman (@svovaf)
4457
		 * @since  1.0.7
4458
		 *
4459
		 * @return bool
4460
		 */
4461
		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...
4462
			return fs_is_plugin_page( $this->_menu->get_raw_slug() ) ||
4463
			       fs_is_plugin_page( $this->_slug );
4464
		}
4465
4466
		/* Events
4467
		------------------------------------------------------------------------------------------------------------------*/
4468
		/**
4469
		 * Delete site install from Database.
4470
		 *
4471
		 * @author Vova Feldman (@svovaf)
4472
		 * @since  1.0.1
4473
		 *
4474
		 * @param bool $store
4475
		 */
4476
		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...
4477
			self::_delete_site_by_slug( $this->_slug, $this->_module_type, $store );
4478
		}
4479
4480
		/**
4481
		 * Delete site install from Database.
4482
		 *
4483
		 * @author Vova Feldman (@svovaf)
4484
		 * @since  1.2.2.7
4485
		 *
4486
		 * @param string $slug
4487
		 * @param string $module_type
4488
		 * @param bool   $store
4489
		 */
4490
		static function _delete_site_by_slug($slug, $module_type, $store = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4491
			$sites = self::get_all_sites( $module_type );
4492
4493
			if ( isset( $sites[ $slug ] ) ) {
4494
				unset( $sites[ $slug ] );
4495
			}
4496
4497
			self::set_account_option_by_module( $module_type, 'sites', $sites, $store );
4498
		}
4499
4500
		/**
4501
		 * Delete plugin's plans information.
4502
		 *
4503
		 * @param bool $store Flush to Database if true.
4504
		 *
4505
		 * @author Vova Feldman (@svovaf)
4506
		 * @since  1.0.9
4507
		 */
4508
		private function _delete_plans( $store = true ) {
4509
			$this->_logger->entrance();
4510
4511
			$plans = self::get_all_plans( $this->_module_type );
4512
4513
			unset( $plans[ $this->_slug ] );
4514
4515
			$this->set_account_option( 'plans', $plans, $store );
4516
		}
4517
4518
		/**
4519
		 * Delete all plugin licenses.
4520
		 *
4521
		 * @author Vova Feldman (@svovaf)
4522
		 * @since  1.0.9
4523
		 *
4524
		 * @param bool        $store
4525
		 * @param string|bool $plugin_slug
4526
		 */
4527
		private function _delete_licenses( $store = true, $plugin_slug = false ) {
4528
			$this->_logger->entrance();
4529
4530
			$all_licenses = self::get_all_licenses( $this->_module_type );
4531
4532
			if ( ! is_string( $plugin_slug ) ) {
4533
				$plugin_slug = $this->_slug;
4534
			}
4535
4536
			unset( $all_licenses[ $plugin_slug ] );
4537
4538
			$this->set_account_option( 'licenses', $all_licenses, $store );
4539
		}
4540
4541
		/**
4542
		 * Check if Freemius was added on new plugin installation.
4543
		 *
4544
		 * @author Vova Feldman (@svovaf)
4545
		 * @since  1.1.5
4546
		 *
4547
		 * @return bool
4548
		 */
4549
		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...
4550
			return isset( $this->_storage->is_plugin_new_install ) &&
4551
			       $this->_storage->is_plugin_new_install;
4552
		}
4553
4554
		/**
4555
		 * Check if it's the first plugin release that is running Freemius.
4556
		 *
4557
		 * @author Vova Feldman (@svovaf)
4558
		 * @since  1.2.1.5
4559
		 *
4560
		 * @return bool
4561
		 */
4562
		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...
4563
			return empty( $this->_storage->plugin_last_version );
4564
		}
4565
4566
		/**
4567
		 * @author Leo Fajardo (@leorw)
4568
		 * @since  1.2.2
4569
		 *
4570
		 * @return bool|string
4571
		 */
4572
		private function get_previous_theme_slug() {
4573
			return isset( $this->_storage->previous_theme ) ?
4574
				$this->_storage->previous_theme :
0 ignored issues
show
Documentation introduced by
The property previous_theme does not exist on object<FS_Key_Value_Storage>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
4575
				false;
4576
		}
4577
4578
		/**
4579
		 * @author Leo Fajardo (@leorw)
4580
		 * @since  1.2.2
4581
		 *
4582
		 * @return string
4583
		 */
4584
		private function can_activate_previous_theme() {
4585
			$slug = $this->get_previous_theme_slug();
4586
			if ( false !== $slug && current_user_can( 'switch_themes' ) ) {
4587
				$theme_instance = wp_get_theme( $slug );
4588
4589
				return $theme_instance->exists();
4590
			}
4591
4592
			return false;
4593
		}
4594
4595
		/**
4596
		 * @author Leo Fajardo (@leorw)
4597
		 * @since  1.2.2
4598
		 *
4599
		 * @return string
4600
		 */
4601
		private function activate_previous_theme() {
4602
			switch_theme( $this->get_previous_theme_slug() );
4603
			unset( $this->_storage->previous_theme );
4604
4605
			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...
4606
			if ( 'themes.php' === $pagenow ) {
4607
				/**
4608
				 * Refresh the active theme information.
4609
				 *
4610
				 * @author Leo Fajardo (@leorw)
4611
				 * @since  1.2.2
4612
				 */
4613
				fs_redirect( admin_url( $pagenow ) );
4614
			}
4615
		}
4616
4617
		/**
4618
		 * @author Leo Fajardo (@leorw)
4619
		 * @since  1.2.2
4620
		 *
4621
		 * @return string
4622
		 */
4623
		function get_previous_theme_activation_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4624
			if ( ! $this->can_activate_previous_theme() ) {
4625
				return '';
4626
			}
4627
4628
			/**
4629
			 * Activation URL
4630
			 *
4631
			 * @author Leo Fajardo (@leorw)
4632
			 * @since  1.2.2
4633
			 */
4634
			return wp_nonce_url(
4635
				admin_url( 'themes.php?action=activate&stylesheet=' . urlencode( $this->get_previous_theme_slug() ) ),
4636
				'switch-theme_' . $this->get_previous_theme_slug()
4637
			);
4638
		}
4639
4640
		/**
4641
		 * Saves the slug of the previous theme if it still exists so that it can be used by the logic in the opt-in
4642
		 * form that decides whether to add a close button to the opt-in dialog or not. So after a premium-only theme is
4643
		 * activated, the close button will appear and will reactivate the previous theme if clicked. If the previous
4644
		 * theme doesn't exist, then there will be no close button.
4645
		 *
4646
		 * @author Leo Fajardo (@leorw)
4647
		 * @since  1.2.2
4648
		 *
4649
		 * @param  string        $slug_or_name Old theme's slug or name.
4650
		 * @param  bool|WP_Theme $old_theme    WP_Theme instance of the old theme if it still exists.
4651
		 */
4652
		function _activate_theme_event_hook( $slug_or_name, $old_theme = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
4654
				$old_theme->get_stylesheet() :
4655
				$slug_or_name;
4656
4657
			$this->_activate_plugin_event_hook();
4658
		}
4659
4660
		/**
4661
		 * Plugin activated hook.
4662
		 *
4663
		 * @author Vova Feldman (@svovaf)
4664
		 * @since  1.0.1
4665
		 *
4666
		 * @uses   FS_Api
4667
		 */
4668
		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...
4669
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4670
4671
			if ( ! $this->is_user_admin() ) {
4672
				return;
4673
			}
4674
4675
			$this->unregister_uninstall_hook();
4676
4677
			// Clear API cache on activation.
4678
			FS_Api::clear_cache();
4679
4680
			$is_premium_version_activation = ( current_filter() !== ( 'activate_' . $this->_free_plugin_basename ) );
4681
4682
			$this->_logger->info( 'Activating ' . ( $is_premium_version_activation ? 'premium' : 'free' ) . ' plugin version.' );
4683
4684
			// 1. If running in the activation of the FREE module, get the basename of the PREMIUM.
4685
			// 2. If running in the activation of the PREMIUM module, get the basename of the FREE.
4686
			$other_version_basename = $is_premium_version_activation ?
4687
				$this->_free_plugin_basename :
4688
				$this->premium_plugin_basename();
4689
4690
			/**
4691
			 * If the other module version is activate, deactivate it.
4692
			 *
4693
			 * @author Leo Fajardo (@leorw)
4694
			 * @since  1.2.2
4695
			 */
4696
			if ( is_plugin_active( $other_version_basename ) ) {
4697
				deactivate_plugins( $other_version_basename );
4698
			}
4699
4700
			if ( $this->is_registered() ) {
4701
				if ( $is_premium_version_activation ) {
4702
					$this->reconnect_locally();
4703
				}
4704
4705
4706
				// 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...
4707
//				$this->sync_install( array(), true );
4708
				$this->schedule_install_sync();
4709
4710
				// If activating the premium module version, add an admin notice to congratulate for an upgrade completion.
4711
				if ( $is_premium_version_activation ) {
4712
					$this->_admin_notices->add(
4713
						sprintf( $this->get_text( 'successful-version-upgrade-message' ), sprintf( '<b>%s</b>', $this->_plugin->title ) ),
4714
						$this->get_text( 'woot' ) . '!'
4715
					);
4716
				}
4717
			} else if ( $this->is_anonymous() ) {
4718
				/**
4719
				 * Reset "skipped" click cache on the following:
4720
				 *  1. Freemius DEV mode.
4721
				 *  2. WordPress DEBUG mode.
4722
				 *  3. If a plugin and the user skipped the exact same version before.
4723
				 *
4724
				 * @since 1.2.2.7 Ulrich Pogson (@grapplerulrich) asked to not reset the SKIPPED flag if the exact same THEME version was activated before unless the developer is running with WP_DEBUG on, or Freemius debug mode on (WP_FS__DEV_MODE).
4725
				 *
4726
				 * @todo 4. If explicitly asked to retry after every activation.
4727
				 */
4728
				if ( WP_FS__DEV_MODE ||
4729
				     (
4730
				     	( $this->is_plugin() || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) &&
4731
				        $this->get_plugin_version() == $this->_storage->is_anonymous['version']
4732
				     )
4733
				) {
4734
					$this->reset_anonymous_mode();
4735
				}
4736
			}
4737
4738
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
4739
				/**
4740
				 * If no previous version of plugin's version exist, it means that it's either
4741
				 * the first time that the plugin installed on the site, or the plugin was installed
4742
				 * before but didn't have Freemius integrated.
4743
				 *
4744
				 * Since register_activation_hook() do NOT fires on updates since 3.1, and only fires
4745
				 * on manual activation via the dashboard, is_plugin_activation() is TRUE
4746
				 * only after immediate activation.
4747
				 *
4748
				 * @since 1.1.4
4749
				 * @link  https://make.wordpress.org/core/2010/10/27/plugin-activation-hooks-no-longer-fire-for-updates/
4750
				 */
4751
				$this->_storage->is_plugin_new_install = empty( $this->_storage->plugin_last_version );
4752
			}
4753
4754
			if ( ! $this->_anonymous_mode &&
4755
			     $this->has_api_connectivity( WP_FS__DEV_MODE ) &&
4756
			     ! $this->_isAutoInstall
4757
			) {
4758
				// Store hint that the plugin was just activated to enable auto-redirection to settings.
4759
				add_option( 'fs_'
4760
				            . ( $this->is_plugin() ? '' : $this->_module_type . '_' )
4761
				            . "{$this->_slug}_activated", true );
4762
			}
4763
4764
			/**
4765
			 * Activation hook is executed after the plugin's main file is loaded, therefore,
4766
			 * after the plugin was loaded. The logic is located at activate_plugin()
4767
			 * ./wp-admin/includes/plugin.php.
4768
			 *
4769
			 * @author Vova Feldman (@svovaf)
4770
			 * @since  1.1.9
4771
			 */
4772
			$this->_storage->was_plugin_loaded = true;
4773
		}
4774
4775
		/**
4776
		 * Delete account.
4777
		 *
4778
		 * @author Vova Feldman (@svovaf)
4779
		 * @since  1.0.3
4780
		 *
4781
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
4782
		 */
4783
		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...
4784
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4785
4786
			if ( $check_user && ! $this->is_user_admin() ) {
4787
				return;
4788
			}
4789
4790
			$this->do_action( 'before_account_delete' );
4791
4792
			// Clear all admin notices.
4793
			$this->_admin_notices->clear_all_sticky();
4794
4795
			$this->_delete_site( false );
4796
4797
			$this->_delete_plans( false );
4798
4799
			$this->_delete_licenses( false );
4800
4801
			// Delete add-ons related to plugin's account.
4802
			$this->_delete_account_addons( false );
4803
4804
			// @todo Delete plans and licenses of add-ons.
4805
4806
			self::$_accounts->store();
4807
4808
			/**
4809
			 * IMPORTANT:
4810
			 *  Clear crons must be executed before clearing all storage.
4811
			 *  Otherwise, the cron will not be cleared.
4812
			 */
4813
			$this->clear_sync_cron();
4814
			$this->clear_install_sync_cron();
4815
4816
			// Clear all storage data.
4817
			$this->_storage->clear_all( true, array(
4818
				'connectivity_test',
4819
				'is_on',
4820
			) );
4821
4822
			// Send delete event.
4823
			$this->get_api_site_scope()->call( '/', 'delete' );
4824
4825
			$this->do_action( 'after_account_delete' );
4826
		}
4827
4828
		/**
4829
		 * Plugin deactivation hook.
4830
		 *
4831
		 * @author Vova Feldman (@svovaf)
4832
		 * @since  1.0.1
4833
		 */
4834
		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...
4835
			$this->_logger->entrance( 'slug = ' . $this->_slug );
4836
4837
			if ( ! current_user_can( 'activate_plugins' ) ) {
4838
				return;
4839
			}
4840
4841
			$this->_admin_notices->clear_all_sticky();
4842
			if ( isset( $this->_storage->sticky_optin_added ) ) {
4843
				unset( $this->_storage->sticky_optin_added );
4844
			}
4845
4846
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
4847
				// Remember that plugin was already installed.
4848
				$this->_storage->is_plugin_new_install = false;
4849
			}
4850
4851
			// Hook to plugin uninstall.
4852
			register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) );
4853
4854
			$this->clear_module_main_file_cache();
4855
			$this->clear_sync_cron();
4856
			$this->clear_install_sync_cron();
4857
4858
			if ( $this->is_registered() ) {
4859
				// Send deactivation event.
4860
				$this->sync_install( array(
4861
					'is_active' => false,
4862
				) );
4863
			} else {
4864
				if ( ! $this->has_api_connectivity() ) {
4865
					// Reset connectivity test cache.
4866
					unset( $this->_storage->connectivity_test );
4867
				}
4868
			}
4869
4870
			// Clear API cache on deactivation.
4871
			FS_Api::clear_cache();
4872
4873
			$this->remove_sdk_reference();
4874
		}
4875
4876
		/**
4877
		 * @author Vova Feldman (@svovaf)
4878
		 * @since  1.1.6
4879
		 */
4880
		private function remove_sdk_reference() {
4881
			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...
4882
4883
			foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) {
4884
				if ( $this->_plugin_basename == $data->plugin_path ) {
4885
					unset( $fs_active_plugins->plugins[ $sdk_path ] );
4886
					break;
4887
				}
4888
			}
4889
4890
			fs_fallback_to_newest_active_sdk();
4891
		}
4892
4893
		/**
4894
		 * @author Vova Feldman (@svovaf)
4895
		 * @since  1.1.3
4896
		 *
4897
		 * @param bool $is_anonymous
4898
		 */
4899
		private function set_anonymous_mode( $is_anonymous = true ) {
4900
			// Store information regarding skip to try and opt-in the user
4901
			// again in the future.
4902
			$this->_storage->is_anonymous = array(
4903
				'is'        => $is_anonymous,
4904
				'timestamp' => WP_FS__SCRIPT_START_TIME,
4905
				'version'   => $this->get_plugin_version(),
4906
			);
4907
4908
			// Update anonymous mode cache.
4909
			$this->_is_anonymous = $is_anonymous;
4910
		}
4911
4912
		/**
4913
		 * @author Vova Feldman (@svovaf)
4914
		 * @since  1.1.3
4915
		 */
4916
		private function reset_anonymous_mode() {
4917
			unset( $this->_storage->is_anonymous );
4918
4919
			/**
4920
			 * Ensure that this field is also "false", otherwise, if the current module's type is "theme" and the module
4921
			 * has no menus, the opt-in popup will not be shown immediately (in this case, the user will have to click
4922
			 * on the admin notice that contains the opt-in link in order to trigger the opt-in popup).
4923
			 *
4924
			 * @author Leo Fajardo (@leorw)
4925
			 * @since  1.2.2
4926
			 */
4927
			unset( $this->_is_anonymous );
4928
		}
4929
4930
		/**
4931
		 * Clears the anonymous mode and redirects to the opt-in screen.
4932
		 *
4933
		 * @author Vova Feldman (@svovaf)
4934
		 * @since  1.1.7
4935
		 */
4936
		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...
4937
			if ( ! $this->is_anonymous() ) {
4938
				return;
4939
			}
4940
4941
			$this->reset_anonymous_mode();
4942
4943
			fs_redirect( $this->get_activation_url() );
4944
		}
4945
4946
		/**
4947
		 * Skip account connect, and set anonymous mode.
4948
		 *
4949
		 * @author Vova Feldman (@svovaf)
4950
		 * @since  1.1.1
4951
		 */
4952
		private function skip_connection() {
4953
			$this->_logger->entrance();
4954
4955
			$this->_admin_notices->remove_sticky( 'connect_account' );
4956
4957
			$this->set_anonymous_mode();
4958
4959
			// Send anonymous skip event.
4960
			// No user identified info nor any tracking will be sent after the user skips the opt-in.
4961
			$this->get_api_plugin_scope()->call( 'skip.json', 'put', array(
4962
				'uid' => $this->get_anonymous_id(),
4963
			) );
4964
		}
4965
4966
		/**
4967
		 * Plugin version update hook.
4968
		 *
4969
		 * @author Vova Feldman (@svovaf)
4970
		 * @since  1.0.4
4971
		 */
4972
		private function update_plugin_version_event() {
4973
			$this->_logger->entrance();
4974
4975
			if ( ! $this->is_registered() ) {
4976
				return;
4977
			}
4978
4979
			$this->schedule_install_sync();
4980
//			$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...
4981
		}
4982
4983
		/**
4984
		 * Return a list of modified plugins since the last sync.
4985
		 *
4986
		 * Note:
4987
		 *  There's no point to store a plugins counter since even if the number of
4988
		 *  plugins didn't change, we still need to check if the versions are all the
4989
		 *  same and the activity state is similar.
4990
		 *
4991
		 * @author Vova Feldman (@svovaf)
4992
		 * @since  1.1.8
4993
		 *
4994
		 * @return array|false
4995
		 */
4996
		private function get_plugins_data_for_api() {
4997
			// Alias.
4998
			$option_name = 'all_plugins';
4999
5000
			$all_cached_plugins = self::$_accounts->get_option( $option_name );
5001
5002
			if ( ! is_object( $all_cached_plugins ) ) {
5003
				$all_cached_plugins = (object) array(
5004
					'timestamp' => '',
5005
					'md5'       => '',
5006
					'plugins'   => array(),
5007
				);
5008
			}
5009
5010
			$time = time();
5011
5012
			if ( ! empty( $all_cached_plugins->timestamp ) &&
5013
			     ( $time - $all_cached_plugins->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC
5014
			) {
5015
				// Don't send plugin updates if last update was in the past 5 min.
5016
				return false;
5017
			}
5018
5019
			// Write timestamp to lock the logic.
5020
			$all_cached_plugins->timestamp = $time;
5021
			self::$_accounts->set_option( $option_name, $all_cached_plugins, true );
5022
5023
			// Reload options from DB.
5024
			self::$_accounts->load( true );
5025
			$all_cached_plugins = self::$_accounts->get_option( $option_name );
5026
5027
			if ( $time != $all_cached_plugins->timestamp ) {
5028
				// If timestamp is different, then another thread captured the lock.
5029
				return false;
5030
			}
5031
5032
			// Check if there's a change in plugins.
5033
			$all_plugins = self::get_all_plugins();
5034
5035
			// Check if plugins changed.
5036
			ksort( $all_plugins );
5037
5038
			$plugins_signature = '';
5039
			foreach ( $all_plugins as $basename => $data ) {
5040
				$plugins_signature .= $data['slug'] . ',' .
5041
				                      $data['Version'] . ',' .
5042
				                      ( $data['is_active'] ? '1' : '0' ) . ';';
5043
			}
5044
5045
			// Check if plugins status changed (version or active/inactive).
5046
			$plugins_changed = ( $all_cached_plugins->md5 !== md5( $plugins_signature ) );
5047
5048
			$plugins_update_data = array();
5049
5050
			if ( $plugins_changed ) {
5051
				// Change in plugins, report changes.
5052
5053
				// Update existing plugins info.
5054
				foreach ( $all_cached_plugins->plugins as $basename => $data ) {
5055
					if ( ! isset( $all_plugins[ $basename ] ) ) {
5056
						// Plugin uninstalled.
5057
						$uninstalled_plugin_data                   = $data;
5058
						$uninstalled_plugin_data['is_active']      = false;
5059
						$uninstalled_plugin_data['is_uninstalled'] = true;
5060
						$plugins_update_data[]                     = $uninstalled_plugin_data;
5061
5062
						unset( $all_plugins[ $basename ] );
5063
						unset( $all_cached_plugins->plugins[ $basename ] );
5064
					} else if ( $data['is_active'] !== $all_plugins[ $basename ]['is_active'] ||
5065
					            $data['version'] !== $all_plugins[ $basename ]['Version']
5066
					) {
5067
						// Plugin activated or deactivated, or version changed.
5068
						$all_cached_plugins->plugins[ $basename ]['is_active'] = $all_plugins[ $basename ]['is_active'];
5069
						$all_cached_plugins->plugins[ $basename ]['version']   = $all_plugins[ $basename ]['Version'];
5070
5071
						$plugins_update_data[] = $all_cached_plugins->plugins[ $basename ];
5072
					}
5073
				}
5074
5075
				// Find new plugins that weren't yet seen before.
5076
				foreach ( $all_plugins as $basename => $data ) {
5077
					if ( ! isset( $all_cached_plugins->plugins[ $basename ] ) ) {
5078
						// New plugin.
5079
						$new_plugin = array(
5080
							'slug'           => $data['slug'],
5081
							'version'        => $data['Version'],
5082
							'title'          => $data['Name'],
5083
							'is_active'      => $data['is_active'],
5084
							'is_uninstalled' => false,
5085
						);
5086
5087
						$plugins_update_data[]                    = $new_plugin;
5088
						$all_cached_plugins->plugins[ $basename ] = $new_plugin;
5089
					}
5090
				}
5091
5092
				$all_cached_plugins->md5       = md5( $plugins_signature );
5093
				$all_cached_plugins->timestamp = $time;
5094
				self::$_accounts->set_option( $option_name, $all_cached_plugins, true );
5095
			}
5096
5097
			return $plugins_update_data;
5098
		}
5099
5100
		/**
5101
		 * Return a list of modified themes since the last sync.
5102
		 *
5103
		 * Note:
5104
		 *  There's no point to store a themes counter since even if the number of
5105
		 *  themes didn't change, we still need to check if the versions are all the
5106
		 *  same and the activity state is similar.
5107
		 *
5108
		 * @author Vova Feldman (@svovaf)
5109
		 * @since  1.1.8
5110
		 *
5111
		 * @return array|false
5112
		 */
5113
		private function get_themes_data_for_api() {
5114
			// Alias.
5115
			$option_name = 'all_themes';
5116
5117
			$all_cached_themes = self::$_accounts->get_option( $option_name );
5118
5119
			if ( ! is_object( $all_cached_themes ) ) {
5120
				$all_cached_themes = (object) array(
5121
					'timestamp' => '',
5122
					'md5'       => '',
5123
					'themes'    => array(),
5124
				);
5125
			}
5126
5127
			$time = time();
5128
5129
			if ( ! empty( $all_cached_themes->timestamp ) &&
5130
			     ( $time - $all_cached_themes->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC
5131
			) {
5132
				// Don't send theme updates if last update was in the past 5 min.
5133
				return false;
5134
			}
5135
5136
			// Write timestamp to lock the logic.
5137
			$all_cached_themes->timestamp = $time;
5138
			self::$_accounts->set_option( $option_name, $all_cached_themes, true );
5139
5140
			// Reload options from DB.
5141
			self::$_accounts->load( true );
5142
			$all_cached_themes = self::$_accounts->get_option( $option_name );
5143
5144
			if ( $time != $all_cached_themes->timestamp ) {
5145
				// If timestamp is different, then another thread captured the lock.
5146
				return false;
5147
			}
5148
5149
			// Get active theme.
5150
			$active_theme            = wp_get_theme();
5151
			$active_theme_stylesheet = $active_theme->get_stylesheet();
5152
5153
			// Check if there's a change in themes.
5154
			$all_themes = wp_get_themes();
5155
5156
			// Check if themes changed.
5157
			ksort( $all_themes );
5158
5159
			$themes_signature = '';
5160
			foreach ( $all_themes as $slug => $data ) {
5161
				$is_active = ( $slug === $active_theme_stylesheet );
5162
				$themes_signature .= $slug . ',' .
5163
				                     $data->version . ',' .
5164
				                     ( $is_active ? '1' : '0' ) . ';';
5165
			}
5166
5167
			// Check if themes status changed (version or active/inactive).
5168
			$themes_changed = ( $all_cached_themes->md5 !== md5( $themes_signature ) );
5169
5170
			$themes_update_data = array();
5171
5172
			if ( $themes_changed ) {
5173
				// Change in themes, report changes.
5174
5175
				// Update existing themes info.
5176
				foreach ( $all_cached_themes->themes as $slug => $data ) {
5177
					$is_active = ( $slug === $active_theme_stylesheet );
5178
5179
					if ( ! isset( $all_themes[ $slug ] ) ) {
5180
						// Plugin uninstalled.
5181
						$uninstalled_theme_data                   = $data;
5182
						$uninstalled_theme_data['is_active']      = false;
5183
						$uninstalled_theme_data['is_uninstalled'] = true;
5184
						$themes_update_data[]                     = $uninstalled_theme_data;
5185
5186
						unset( $all_themes[ $slug ] );
5187
						unset( $all_cached_themes->themes[ $slug ] );
5188
					} else if ( $data['is_active'] !== $is_active ||
5189
					            $data['version'] !== $all_themes[ $slug ]->version
5190
					) {
5191
						// Plugin activated or deactivated, or version changed.
5192
5193
						$all_cached_themes->themes[ $slug ]['is_active'] = $is_active;
5194
						$all_cached_themes->themes[ $slug ]['version']   = $all_themes[ $slug ]->version;
5195
5196
						$themes_update_data[] = $all_cached_themes->themes[ $slug ];
5197
					}
5198
				}
5199
5200
				// Find new themes that weren't yet seen before.
5201
				foreach ( $all_themes as $slug => $data ) {
5202
					if ( ! isset( $all_cached_themes->themes[ $slug ] ) ) {
5203
						$is_active = ( $slug === $active_theme_stylesheet );
5204
5205
						// New plugin.
5206
						$new_plugin = array(
5207
							'slug'           => $slug,
5208
							'version'        => $data->version,
5209
							'title'          => $data->name,
5210
							'is_active'      => $is_active,
5211
							'is_uninstalled' => false,
5212
						);
5213
5214
						$themes_update_data[]               = $new_plugin;
5215
						$all_cached_themes->themes[ $slug ] = $new_plugin;
5216
					}
5217
				}
5218
5219
				$all_cached_themes->md5       = md5( $themes_signature );
5220
				$all_cached_themes->timestamp = time();
5221
				self::$_accounts->set_option( $option_name, $all_cached_themes, true );
5222
			}
5223
5224
			return $themes_update_data;
5225
		}
5226
5227
		/**
5228
		 * Update install details.
5229
		 *
5230
		 * @author Vova Feldman (@svovaf)
5231
		 * @since  1.1.2
5232
		 *
5233
		 * @param string[] string           $override
5234
		 * @param bool     $include_plugins Since 1.1.8 by default include plugin changes.
5235
		 * @param bool     $include_themes  Since 1.1.8 by default include plugin changes.
5236
		 *
5237
		 * @return array
5238
		 */
5239
		private function get_install_data_for_api(
5240
			array $override,
5241
			$include_plugins = true,
5242
			$include_themes = true
5243
		) {
5244
			/**
5245
			 * @since 1.1.8 Also send plugin updates.
5246
			 */
5247
			if ( $include_plugins && ! isset( $override['plugins'] ) ) {
5248
				$plugins = $this->get_plugins_data_for_api();
5249
				if ( ! empty( $plugins ) ) {
5250
					$override['plugins'] = $plugins;
5251
				}
5252
			}
5253
			/**
5254
			 * @since 1.1.8 Also send themes updates.
5255
			 */
5256
			if ( $include_themes && ! isset( $override['themes'] ) ) {
5257
				$themes = $this->get_themes_data_for_api();
5258
				if ( ! empty( $themes ) ) {
5259
					$override['themes'] = $themes;
5260
				}
5261
			}
5262
5263
			return array_merge( array(
5264
				'version'                      => $this->get_plugin_version(),
5265
				'is_premium'                   => $this->is_premium(),
5266
				'language'                     => get_bloginfo( 'language' ),
5267
				'charset'                      => get_bloginfo( 'charset' ),
5268
				'platform_version'             => get_bloginfo( 'version' ),
5269
				'sdk_version'                  => $this->version,
5270
				'programming_language_version' => phpversion(),
5271
				'title'                        => get_bloginfo( 'name' ),
5272
				'url'                          => get_site_url(),
5273
				// Special params.
5274
				'is_active'                    => true,
5275
				'is_disconnected'              => $this->is_tracking_prohibited(),
5276
				'is_uninstalled'               => false,
5277
			), $override );
5278
		}
5279
5280
		/**
5281
		 * Update install only if changed.
5282
		 *
5283
		 * @author Vova Feldman (@svovaf)
5284
		 * @since  1.0.9
5285
		 *
5286
		 * @param string[] string $override
5287
		 * @param bool     $flush
5288
		 *
5289
		 * @return false|object|string
5290
		 */
5291
		private function send_install_update( $override = array(), $flush = false ) {
5292
			$this->_logger->entrance();
5293
5294
			$check_properties = $this->get_install_data_for_api( $override );
5295
5296
			if ( $flush ) {
5297
				$params = $check_properties;
5298
			} else {
5299
				$params           = array();
5300
				$special          = array();
5301
				$special_override = false;
5302
5303
				foreach ( $check_properties as $p => $v ) {
5304
					if ( property_exists( $this->_site, $p ) ) {
5305
						if ( ( is_bool( $this->_site->{$p} ) || ! empty( $this->_site->{$p} ) ) &&
5306
						     $this->_site->{$p} != $v
5307
						) {
5308
							$this->_site->{$p} = $v;
5309
							$params[ $p ]      = $v;
5310
						}
5311
					} else {
5312
						$special[ $p ] = $v;
5313
5314
						if ( isset( $override[ $p ] ) ||
5315
						     'plugins' === $p ||
5316
						     'themes' === $p
5317
						) {
5318
							$special_override = true;
5319
						}
5320
					}
5321
				}
5322
5323
				if ( $special_override || 0 < count( $params ) ) {
5324
					// Add special params only if has at least one
5325
					// standard param, or if explicitly requested to
5326
					// override a special param or a param which is not exist
5327
					// in the install object.
5328
					$params = array_merge( $params, $special );
5329
				}
5330
			}
5331
5332
			if ( 0 < count( $params ) ) {
5333
				// Update last install sync timestamp.
5334
				$this->_storage->install_sync_timestamp = time();
5335
5336
				$params['uid'] = $this->get_anonymous_id();
5337
5338
				// Send updated values to FS.
5339
				$site = $this->get_api_site_scope()->call( '/', 'put', $params );
5340
5341
				if ( $this->is_api_result_entity( $site ) ) {
5342
					// I successfully sent install update, clear scheduled sync if exist.
5343
					$this->clear_install_sync_cron();
5344
				}
5345
5346
				return $site;
5347
			}
5348
5349
			return false;
5350
		}
5351
5352
		/**
5353
		 * Update install only if changed.
5354
		 *
5355
		 * @author Vova Feldman (@svovaf)
5356
		 * @since  1.0.9
5357
		 *
5358
		 * @param string[] string $override
5359
		 * @param bool     $flush
5360
		 */
5361
		private function sync_install( $override = array(), $flush = false ) {
5362
			$this->_logger->entrance();
5363
5364
			$site = $this->send_install_update( $override, $flush );
5365
5366
			if ( false === $site ) {
5367
				// No sync required.
5368
				return;
5369
			}
5370
5371
			if ( ! $this->is_api_result_entity( $site ) ) {
5372
				// Failed to sync, don't update locally.
5373
				return;
5374
			}
5375
5376
			$plan              = $this->get_plan();
5377
			$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...
5378
			$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...
5379
5380
			$this->_store_site( true );
5381
		}
5382
5383
		/**
5384
		 * Track install's custom event.
5385
		 *
5386
		 * IMPORTANT:
5387
		 *      Custom event tracking is currently only supported for specific clients.
5388
		 *      If you are not one of them, please don't use this method. If you will,
5389
		 *      the API will simply ignore your request based on the plugin ID.
5390
		 *
5391
		 * Need custom tracking for your plugin or theme?
5392
		 *      If you are interested in custom event tracking please contact [email protected]
5393
		 *      for further details.
5394
		 *
5395
		 * @author Vova Feldman (@svovaf)
5396
		 * @since  1.2.1
5397
		 *
5398
		 * @param string $name       Event name.
5399
		 * @param array  $properties Associative key/value array with primitive values only
5400
		 * @param bool   $process_at A valid future date-time in the following format Y-m-d H:i:s.
5401
		 * @param bool   $once       If true, event will be tracked only once. IMPORTANT: Still trigger the API call.
5402
		 *
5403
		 * @return object|false Event data or FALSE on failure.
5404
		 *
5405
		 * @throws \Freemius_InvalidArgumentException
5406
		 */
5407
		public function track_event( $name, $properties = array(), $process_at = false, $once = false ) {
5408
			$this->_logger->entrance( http_build_query( array( 'name' => $name, 'once' => $once ) ) );
5409
5410
			if ( ! $this->is_registered() ) {
5411
				return false;
5412
			}
5413
5414
			$event = array( 'type' => $name );
5415
5416
			if ( is_numeric( $process_at ) && $process_at > time() ) {
5417
				$event['process_at'] = $process_at;
5418
			}
5419
5420
			if ( $once ) {
5421
				$event['once'] = true;
5422
			}
5423
5424
			if ( ! empty( $properties ) ) {
5425
				// Verify associative array values are primitive.
5426
				foreach ( $properties as $k => $v ) {
5427
					if ( ! is_scalar( $v ) ) {
5428
						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...
5429
					}
5430
				}
5431
5432
				$event['properties'] = $properties;
5433
			}
5434
5435
			$result = $this->get_api_site_scope()->call( 'events.json', 'post', $event );
5436
5437
			return $this->is_api_error( $result ) ?
5438
				false :
5439
				$result;
5440
		}
5441
5442
		/**
5443
		 * Track install's custom event only once, but it still triggers the API call.
5444
		 *
5445
		 * IMPORTANT:
5446
		 *      Custom event tracking is currently only supported for specific clients.
5447
		 *      If you are not one of them, please don't use this method. If you will,
5448
		 *      the API will simply ignore your request based on the plugin ID.
5449
		 *
5450
		 * Need custom tracking for your plugin or theme?
5451
		 *      If you are interested in custom event tracking please contact [email protected]
5452
		 *      for further details.
5453
		 *
5454
		 * @author Vova Feldman (@svovaf)
5455
		 * @since  1.2.1
5456
		 *
5457
		 * @param string $name       Event name.
5458
		 * @param array  $properties Associative key/value array with primitive values only
5459
		 * @param bool   $process_at A valid future date-time in the following format Y-m-d H:i:s.
5460
		 *
5461
		 * @return object|false Event data or FALSE on failure.
5462
		 *
5463
		 * @throws \Freemius_InvalidArgumentException
5464
		 *
5465
		 * @user   Freemius::track_event()
5466
		 */
5467
		public function track_event_once( $name, $properties = array(), $process_at = false ) {
5468
			return $this->track_event( $name, $properties, $process_at, true );
5469
		}
5470
5471
		/**
5472
		 * Plugin uninstall hook.
5473
		 *
5474
		 * @author Vova Feldman (@svovaf)
5475
		 * @since  1.0.1
5476
		 *
5477
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
5478
		 */
5479
		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...
5480
			$this->_logger->entrance( 'slug = ' . $this->_slug );
5481
5482
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
5483
				return;
5484
			}
5485
5486
			$params           = array();
5487
			$uninstall_reason = null;
5488
			if ( isset( $this->_storage->uninstall_reason ) ) {
5489
				$uninstall_reason      = $this->_storage->uninstall_reason;
5490
				$params['reason_id']   = $uninstall_reason->id;
5491
				$params['reason_info'] = $uninstall_reason->info;
5492
			}
5493
5494
			if ( ! $this->is_registered() ) {
5495
				// Send anonymous uninstall event only if user submitted a feedback.
5496
				if ( isset( $uninstall_reason ) ) {
5497
					if ( isset( $uninstall_reason->is_anonymous ) && ! $uninstall_reason->is_anonymous ) {
5498
						$this->opt_in( false, false, false, false, true );
5499
					} else {
5500
						$params['uid'] = $this->get_anonymous_id();
5501
						$this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params );
5502
					}
5503
				}
5504
			} else {
5505
				// Send uninstall event.
5506
				$this->send_install_update( array_merge( $params, array(
5507
					'is_active'      => false,
5508
					'is_uninstalled' => true,
5509
				) ) );
5510
			}
5511
5512
			// @todo Decide if we want to delete plugin information from db.
5513
		}
5514
5515
		/**
5516
		 * @author Vova Feldman (@svovaf)
5517
		 * @since  1.1.1
5518
		 *
5519
		 * @return string
5520
		 */
5521
		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...
5522
			return "{$this->_slug}-premium/" . basename( $this->_free_plugin_basename );
5523
		}
5524
5525
		/**
5526
		 * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking.
5527
		 *
5528
		 * @author Vova Feldman (@svovaf)
5529
		 * @since  1.0.2
5530
		 */
5531
		public static function _uninstall_plugin_hook() {
5532
			self::_load_required_static();
5533
5534
			self::$_static_logger->entrance();
5535
5536
			if ( ! current_user_can( 'activate_plugins' ) ) {
5537
				return;
5538
			}
5539
5540
			$plugin_file = substr( current_filter(), strlen( 'uninstall_' ) );
5541
5542
			self::$_static_logger->info( 'plugin = ' . $plugin_file );
5543
5544
			define( 'WP_FS__UNINSTALL_MODE', true );
5545
5546
			$fs = self::get_instance_by_file( $plugin_file );
5547
5548
			if ( is_object( $fs ) ) {
5549
				self::require_plugin_essentials();
5550
5551
				if ( is_plugin_active( $fs->_free_plugin_basename ) ||
5552
				     is_plugin_active( $fs->premium_plugin_basename() )
5553
				) {
5554
					// Deleting Free or Premium plugin version while the other version still installed.
5555
					return;
5556
				}
5557
5558
				$fs->_uninstall_plugin_event();
5559
5560
				$fs->do_action( 'after_uninstall' );
5561
			}
5562
		}
5563
5564
		#----------------------------------------------------------------------------------
5565
		#region Plugin Information
5566
		#----------------------------------------------------------------------------------
5567
5568
		/**
5569
		 * Load WordPress core plugin.php essential module.
5570
		 *
5571
		 * @author Vova Feldman (@svovaf)
5572
		 * @since  1.1.1
5573
		 */
5574
		private static function require_plugin_essentials() {
5575
			if ( ! function_exists( 'get_plugins' ) ) {
5576
				self::$_static_logger->log( 'Including wp-admin/includes/plugin.php...' );
5577
5578
				require_once ABSPATH . 'wp-admin/includes/plugin.php';
5579
			}
5580
		}
5581
5582
		/**
5583
		 * Load WordPress core pluggable.php module.
5584
		 *
5585
		 * @author Vova Feldman (@svovaf)
5586
		 * @since  1.1.2
5587
		 */
5588
		private static function require_pluggable_essentials() {
5589
			if ( ! function_exists( 'wp_get_current_user' ) ) {
5590
				require_once ABSPATH . 'wp-includes/pluggable.php';
5591
			}
5592
		}
5593
5594
		/**
5595
		 * Return plugin data.
5596
		 *
5597
		 * @author Vova Feldman (@svovaf)
5598
		 * @since  1.0.1
5599
		 *
5600
		 * @return array
5601
		 */
5602
		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...
5603
			if ( ! isset( $this->_plugin_data ) ) {
5604
				self::require_plugin_essentials();
5605
5606
				if ( $this->is_plugin() ) {
5607
					/**
5608
					 * @author Vova Feldman (@svovaf)
5609
					 * @since  1.2.0 When using get_plugin_data() do NOT translate plugin data.
5610
					 *
5611
					 * @link   https://github.com/Freemius/wordpress-sdk/issues/77
5612
					 */
5613
					$plugin_data = get_plugin_data(
5614
						$this->_plugin_main_file_path,
5615
						false,
5616
						false
5617
					);
5618
				} else {
5619
					$theme_data = wp_get_theme();
5620
5621
					$plugin_data = array(
5622
						'Name'        => $theme_data->get( 'Name' ),
5623
						'Version'     => $theme_data->get( 'Version' ),
5624
						'Author'      => $theme_data->get( 'Author' ),
5625
						'Description' => $theme_data->get( 'Description' ),
5626
						'PluginURI'   => $theme_data->get( 'ThemeURI' ),
5627
					);
5628
				}
5629
5630
				$this->_plugin_data = $plugin_data;
5631
			}
5632
5633
			return $this->_plugin_data;
5634
		}
5635
5636
		/**
5637
		 * @author Vova Feldman (@svovaf)
5638
		 * @since  1.0.1
5639
		 * @since  1.2.2.5 If slug not set load slug by module ID.
5640
		 *
5641
		 * @return string Plugin slug.
5642
		 */
5643
		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...
5644
			if ( ! isset( $this->_slug ) ) {
5645
				$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
5646
				$this->_slug           = $id_slug_type_path_map[ $this->_module_id ]['slug'];
5647
			}
5648
5649
			return $this->_slug;
5650
		}
5651
5652
		/**
5653
		 * @author Vova Feldman (@svovaf)
5654
		 * @since  1.2.1.7
5655
		 *
5656
		 * @return string Plugin slug.
5657
		 */
5658
		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...
5659
			return $this->_slug . ( $this->can_use_premium_code() ? '-premium' : '' );
5660
		}
5661
5662
		/**
5663
		 * @author Vova Feldman (@svovaf)
5664
		 * @since  1.0.1
5665
		 *
5666
		 * @return number Plugin ID.
5667
		 */
5668
		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...
5669
			return $this->_plugin->id;
5670
		}
5671
5672
		/**
5673
		 * @author Vova Feldman (@svovaf)
5674
		 * @since  1.2.1.5
5675
		 *
5676
		 * @return string Freemius SDK version
5677
		 */
5678
		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...
5679
			return $this->version;
5680
		}
5681
5682
		/**
5683
		 * @author Vova Feldman (@svovaf)
5684
		 * @since  1.2.1.5
5685
		 *
5686
		 * @return number Parent plugin ID (if parent exist).
5687
		 */
5688
		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...
5689
			return $this->is_addon() ?
5690
				$this->get_parent_instance()->get_id() :
5691
				$this->_plugin->id;
5692
		}
5693
5694
		/**
5695
		 * @author Vova Feldman (@svovaf)
5696
		 * @since  1.0.1
5697
		 *
5698
		 * @return string Plugin public key.
5699
		 */
5700
		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...
5701
			return $this->_plugin->public_key;
5702
		}
5703
5704
		/**
5705
		 * Will be available only on sandbox mode.
5706
		 *
5707
		 * @author Vova Feldman (@svovaf)
5708
		 * @since  1.0.4
5709
		 *
5710
		 * @return mixed Plugin secret key.
5711
		 */
5712
		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...
5713
			return $this->_plugin->secret_key;
5714
		}
5715
5716
		/**
5717
		 * @author Vova Feldman (@svovaf)
5718
		 * @since  1.1.1
5719
		 *
5720
		 * @return bool
5721
		 */
5722
		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...
5723
			return ! empty( $this->_plugin->secret_key );
5724
		}
5725
5726
		/**
5727
		 * @author Vova Feldman (@svovaf)
5728
		 * @since  1.0.9
5729
		 *
5730
		 * @return string
5731
		 */
5732
		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...
5733
			$this->_logger->entrance();
5734
5735
			if ( ! isset( $this->_plugin_name ) ) {
5736
				$plugin_data = $this->get_plugin_data();
5737
5738
				// Get name.
5739
				$this->_plugin_name = $plugin_data['Name'];
5740
5741
				// Check if plugin name contains "(Premium)" suffix and remove it.
5742
				$suffix     = ' (premium)';
5743
				$suffix_len = strlen( $suffix );
5744
5745
				if ( strlen( $plugin_data['Name'] ) > $suffix_len &&
5746
				     $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len )
5747
				) {
5748
					$this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len );
5749
				}
5750
5751
				$this->_logger->departure( 'Name = ' . $this->_plugin_name );
5752
			}
5753
5754
			return $this->_plugin_name;
5755
		}
5756
5757
		/**
5758
		 * @author Vova Feldman (@svovaf)
5759
		 * @since  1.0.0
5760
		 *
5761
		 * @return string
5762
		 */
5763
		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...
5764
			$this->_logger->entrance();
5765
5766
			$plugin_data = $this->get_plugin_data();
5767
5768
			$this->_logger->departure( 'Version = ' . $plugin_data['Version'] );
5769
5770
			return $this->apply_filters( 'plugin_version', $plugin_data['Version'] );
5771
		}
5772
5773
		/**
5774
		 * @author Vova Feldman (@svovaf)
5775
		 * @since  1.2.1.7
5776
		 *
5777
		 * @return string
5778
		 */
5779
		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...
5780
			$this->_logger->entrance();
5781
5782
			$title = $this->_plugin->title;
5783
5784
			return $this->apply_filters( 'plugin_title', $title );
5785
		}
5786
5787
		/**
5788
		 * @author Vova Feldman (@svovaf)
5789
		 * @since 1.2.2.7
5790
		 *
5791
		 * @param bool $lowercase
5792
		 *
5793
		 * @return string
5794
		 */
5795
		function get_module_label( $lowercase = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5796
			$label = $this->is_addon() ?
5797
				$this->get_text( 'addon' ) :
5798
				( $this->is_plugin() ?
5799
					$this->get_text( 'plugin' ) :
5800
					$this->get_text( 'theme' ) );
5801
5802
			if ( $lowercase ) {
5803
				$label = strtolower( $lowercase );
5804
			}
5805
5806
			return $label;
5807
		}
5808
5809
		/**
5810
		 * @author Vova Feldman (@svovaf)
5811
		 * @since  1.0.4
5812
		 *
5813
		 * @return string
5814
		 */
5815
		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...
5816
			if ( ! isset( $this->_plugin_basename ) ) {
5817
				if ( $this->is_plugin() ) {
5818
					$this->_plugin_basename = plugin_basename( $this->_plugin_main_file_path );
5819
				} else {
5820
					$this->_plugin_basename = basename( dirname( $this->_plugin_main_file_path ) );
5821
				}
5822
			}
5823
5824
			return $this->_plugin_basename;
5825
		}
5826
5827
		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...
5828
			$this->_logger->entrance();
5829
5830
			$plugin_folder = $this->_plugin_basename;
5831
5832
			while ( '.' !== dirname( $plugin_folder ) ) {
5833
				$plugin_folder = dirname( $plugin_folder );
5834
			}
5835
5836
			$this->_logger->departure( 'Folder Name = ' . $plugin_folder );
5837
5838
			return $plugin_folder;
5839
		}
5840
5841
		#endregion ------------------------------------------------------------------
5842
5843
		/* Account
5844
		------------------------------------------------------------------------------------------------------------------*/
5845
5846
		/**
5847
		 * Find plugin's slug by plugin's basename.
5848
		 *
5849
		 * @author Vova Feldman (@svovaf)
5850
		 * @since  1.0.9
5851
		 *
5852
		 * @param string $plugin_base_name
5853
		 *
5854
		 * @return false|string
5855
		 */
5856
		private static function find_slug_by_basename( $plugin_base_name ) {
5857
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
5858
5859
			if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) {
5860
				return false;
5861
			}
5862
5863
			return $file_slug_map[ $plugin_base_name ];
5864
		}
5865
5866
		/**
5867
		 * Store the map between the plugin's basename to the slug.
5868
		 *
5869
		 * @author Vova Feldman (@svovaf)
5870
		 * @since  1.0.9
5871
		 */
5872
		private function store_file_slug_map() {
5873
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
5874
5875
			if ( ! array( $file_slug_map ) ) {
5876
				$file_slug_map = array();
5877
			}
5878
5879
			if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) ||
5880
			     $file_slug_map[ $this->_plugin_basename ] !== $this->_slug
5881
			) {
5882
				$file_slug_map[ $this->_plugin_basename ] = $this->_slug;
5883
				self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true );
5884
			}
5885
		}
5886
5887
		/**
5888
		 * @return FS_User[]
5889
		 */
5890
		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...
5891
			$users = self::$_accounts->get_option( 'users', array() );
5892
5893
			if ( ! is_array( $users ) ) {
5894
				$users = array();
5895
			}
5896
5897
			return $users;
5898
		}
5899
5900
		/**
5901
		 * @param string $module_type
5902
		 *
5903
		 * @return FS_Site[]
5904
		 */
5905
		private static function get_all_sites( $module_type = WP_FS__MODULE_TYPE_PLUGIN ) {
5906
			$sites = self::get_account_option( 'sites', $module_type );
5907
5908
			if ( ! is_array( $sites ) ) {
5909
				$sites = array();
5910
			}
5911
5912
			return $sites;
5913
		}
5914
5915
		/**
5916
		 * @author Leo Fajardo (@leorw)
5917
		 *
5918
		 * @since  1.2.2
5919
		 *
5920
		 * @param string $option_name
5921
		 * @param string $module_type
5922
		 *
5923
		 * @return mixed
5924
		 */
5925
		private static function get_account_option( $option_name, $module_type ) {
5926
			if ( WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) {
5927
				$option_name = $module_type . '_' . $option_name;
5928
			}
5929
5930
			return self::$_accounts->get_option( $option_name, array() );
5931
		}
5932
5933
		/**
5934
		 * @author Leo Fajardo (@leorw)
5935
		 *
5936
		 * @since  1.2.2
5937
		 *
5938
		 * @param string $option_name
5939
		 * @param mixed  $option_value
5940
		 * @param bool   $store
5941
		 */
5942
		private function set_account_option( $option_name, $option_value, $store ) {
5943
			self::set_account_option_by_module(
5944
				$this->_module_type,
5945
				$option_name,
5946
				$option_value,
5947
				$store
5948
			);
5949
		}
5950
5951
		/**
5952
		 * @author Vova Feldman (@svovaf)
5953
		 *
5954
		 * @since  1.2.2.7
5955
		 *
5956
		 * @param string $module_type
5957
		 * @param string $option_name
5958
		 * @param mixed  $option_value
5959
		 * @param bool   $store
5960
		 */
5961
		private static function set_account_option_by_module( $module_type, $option_name, $option_value, $store ) {
5962
			if ( WP_FS__MODULE_TYPE_PLUGIN != $module_type ) {
5963
				$option_name = $module_type . '_' . $option_name;
5964
			}
5965
5966
			self::$_accounts->set_option( $option_name, $option_value, $store );
5967
		}
5968
5969
		/**
5970
		 * @author Vova Feldman (@svovaf)
5971
		 * @since  1.0.6
5972
		 *
5973
		 * @param string $module_type
5974
		 *
5975
		 * @return FS_Plugin_License[]
5976
		 */
5977
		private static function get_all_licenses( $module_type = WP_FS__MODULE_TYPE_PLUGIN ) {
5978
			$licenses = self::get_account_option( 'licenses', $module_type );
5979
5980
			if ( ! is_array( $licenses ) ) {
5981
				$licenses = array();
5982
			}
5983
5984
			return $licenses;
5985
		}
5986
5987
		/**
5988
		 * @param string|bool $module_type
5989
		 *
5990
		 * @return FS_Plugin_Plan[]
5991
		 */
5992
		private static function get_all_plans( $module_type = false ) {
5993
			$plans = self::get_account_option( 'plans', $module_type );
0 ignored issues
show
Bug introduced by
It seems like $module_type defined by parameter $module_type on line 5992 can also be of type boolean; however, Freemius::get_account_option() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
5994
5995
			if ( ! is_array( $plans ) ) {
5996
				$plans = array();
5997
			}
5998
5999
			return $plans;
6000
		}
6001
6002
		/**
6003
		 * @author Vova Feldman (@svovaf)
6004
		 * @since  1.0.4
6005
		 *
6006
		 * @return FS_Plugin_Tag[]
6007
		 */
6008
		private static function get_all_updates() {
6009
			$updates = self::$_accounts->get_option( 'updates', array() );
6010
6011
			if ( ! is_array( $updates ) ) {
6012
				$updates = array();
6013
			}
6014
6015
			return $updates;
6016
		}
6017
6018
		/**
6019
		 * @author Vova Feldman (@svovaf)
6020
		 * @since  1.0.6
6021
		 *
6022
		 * @return array<number,FS_Plugin[]>|false
6023
		 */
6024
		private static function get_all_addons() {
6025
			$addons = self::$_accounts->get_option( 'addons', array() );
6026
6027
			if ( ! is_array( $addons ) ) {
6028
				$addons = array();
6029
			}
6030
6031
			return $addons;
6032
		}
6033
6034
		/**
6035
		 * @author Vova Feldman (@svovaf)
6036
		 * @since  1.0.6
6037
		 *
6038
		 * @return FS_Plugin[]|false
6039
		 */
6040
		private static function get_all_account_addons() {
6041
			$addons = self::$_accounts->get_option( 'account_addons', array() );
6042
6043
			if ( ! is_array( $addons ) ) {
6044
				$addons = array();
6045
			}
6046
6047
			return $addons;
6048
		}
6049
6050
		/**
6051
		 * Check if user has connected his account (opted-in).
6052
		 *
6053
		 * Note:
6054
		 *      If the user opted-in and opted-out on a later stage,
6055
		 *      this will still return true. If you want to check if the
6056
		 *      user is currently opted-in, use:
6057
		 *          `$fs->is_registered() && $fs->is_tracking_allowed()`
6058
		 *
6059
		 * @author Vova Feldman (@svovaf)
6060
		 * @since  1.0.1
6061
		 * @return bool
6062
		 */
6063
		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...
6064
			return is_object( $this->_user );
6065
		}
6066
6067
		/**
6068
		 * Returns TRUE if the user opted-in and didn't disconnect (opt-out).
6069
		 *
6070
		 * @author Leo Fajardo (@leorw)
6071
		 * @since  1.2.1.5
6072
		 *
6073
		 * @return bool
6074
		 */
6075
		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...
6076
			return ( is_object( $this->_site ) && true !== $this->_site->is_disconnected );
6077
		}
6078
6079
		/**
6080
		 * @author Vova Feldman (@svovaf)
6081
		 * @since  1.0.4
6082
		 *
6083
		 * @return FS_Plugin
6084
		 */
6085
		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...
6086
			return $this->_plugin;
6087
		}
6088
6089
		/**
6090
		 * @author Vova Feldman (@svovaf)
6091
		 * @since  1.0.3
6092
		 *
6093
		 * @return FS_User
6094
		 */
6095
		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...
6096
			return $this->_user;
6097
		}
6098
6099
		/**
6100
		 * @author Vova Feldman (@svovaf)
6101
		 * @since  1.0.3
6102
		 *
6103
		 * @return FS_Site
6104
		 */
6105
		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...
6106
			return $this->_site;
6107
		}
6108
6109
		/**
6110
		 * Get plugin add-ons.
6111
		 *
6112
		 * @author Vova Feldman (@svovaf)
6113
		 * @since  1.0.6
6114
		 *
6115
		 * @since  1.1.7.3 If not yet loaded, fetch data from the API.
6116
		 *
6117
		 * @param bool $flush
6118
		 *
6119
		 * @return FS_Plugin[]|false
6120
		 */
6121
		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...
6122
			$this->_logger->entrance();
6123
6124
			if ( ! $this->_has_addons ) {
6125
				return false;
6126
			}
6127
6128
			$addons = $this->sync_addons( $flush );
6129
6130
			return ( ! is_array( $addons ) || empty( $addons ) ) ?
6131
				false :
6132
				$addons;
6133
		}
6134
6135
		/**
6136
		 * @author Vova Feldman (@svovaf)
6137
		 * @since  1.0.6
6138
		 *
6139
		 * @return FS_Plugin[]|false
6140
		 */
6141
		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...
6142
			$this->_logger->entrance();
6143
6144
			$addons = self::get_all_account_addons();
6145
6146
			if ( ! is_array( $addons ) ||
6147
			     ! isset( $addons[ $this->_plugin->id ] ) ||
6148
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
6149
			     0 === count( $addons[ $this->_plugin->id ] )
6150
			) {
6151
				return false;
6152
			}
6153
6154
			return $addons[ $this->_plugin->id ];
6155
		}
6156
6157
		/**
6158
		 * Check if user has any
6159
		 *
6160
		 * @author Vova Feldman (@svovaf)
6161
		 * @since  1.1.6
6162
		 *
6163
		 * @return bool
6164
		 */
6165
		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...
6166
			$addons = $this->get_account_addons();
6167
6168
			return is_array( $addons ) && ( 0 < count( $addons ) );
6169
		}
6170
6171
6172
		/**
6173
		 * Get add-on by ID (from local data).
6174
		 *
6175
		 * @author Vova Feldman (@svovaf)
6176
		 * @since  1.0.6
6177
		 *
6178
		 * @param number $id
6179
		 *
6180
		 * @return FS_Plugin|false
6181
		 */
6182
		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...
6183
			$this->_logger->entrance();
6184
6185
			$addons = $this->get_addons();
6186
6187
			if ( is_array( $addons ) ) {
6188
				foreach ( $addons as $addon ) {
6189
					if ( $id == $addon->id ) {
6190
						return $addon;
6191
					}
6192
				}
6193
			}
6194
6195
			return false;
6196
		}
6197
6198
		/**
6199
		 * Get add-on by slug (from local data).
6200
		 *
6201
		 * @author Vova Feldman (@svovaf)
6202
		 * @since  1.0.6
6203
		 *
6204
		 * @param string $slug
6205
		 *
6206
		 * @param bool   $flush
6207
		 *
6208
		 * @return FS_Plugin|false
6209
		 */
6210
		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...
6211
			$this->_logger->entrance();
6212
6213
			$addons = $this->get_addons( $flush );
6214
6215
			if ( is_array( $addons ) ) {
6216
				foreach ( $addons as $addon ) {
6217
					if ( $slug === $addon->slug ) {
6218
						return $addon;
6219
					}
6220
				}
6221
			}
6222
6223
			return false;
6224
		}
6225
6226
		#----------------------------------------------------------------------------------
6227
		#region Plans & Licensing
6228
		#----------------------------------------------------------------------------------
6229
6230
		/**
6231
		 * Check if running premium plugin code.
6232
		 *
6233
		 * @author Vova Feldman (@svovaf)
6234
		 * @since  1.0.5
6235
		 *
6236
		 * @return bool
6237
		 */
6238
		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...
6239
			return $this->_plugin->is_premium;
6240
		}
6241
6242
		/**
6243
		 * Get site's plan ID.
6244
		 *
6245
		 * @author Vova Feldman (@svovaf)
6246
		 * @since  1.0.2
6247
		 *
6248
		 * @return number
6249
		 */
6250
		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...
6251
			return $this->_site->plan->id;
6252
		}
6253
6254
		/**
6255
		 * Get site's plan title.
6256
		 *
6257
		 * @author Vova Feldman (@svovaf)
6258
		 * @since  1.0.2
6259
		 *
6260
		 * @return string
6261
		 */
6262
		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...
6263
			return $this->_site->plan->title;
6264
		}
6265
6266
		/**
6267
		 * @author Vova Feldman (@svovaf)
6268
		 * @since  1.0.9
6269
		 *
6270
		 * @return FS_Plugin_Plan|false
6271
		 */
6272
		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...
6273
			return is_object( $this->_site->plan ) ?
6274
				$this->_site->plan :
6275
				false;
6276
		}
6277
6278
		/**
6279
		 * @author Vova Feldman (@svovaf)
6280
		 * @since  1.0.3
6281
		 *
6282
		 * @return bool
6283
		 */
6284
		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...
6285
			$this->_logger->entrance();
6286
6287
			if ( ! $this->is_registered() ) {
6288
				return false;
6289
			}
6290
6291
			return $this->_site->is_trial();
6292
		}
6293
6294
		/**
6295
		 * Check if currently in a trial with payment method (credit card or paypal).
6296
		 *
6297
		 * @author Vova Feldman (@svovaf)
6298
		 * @since  1.1.7
6299
		 *
6300
		 * @return bool
6301
		 */
6302
		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...
6303
			$this->_logger->entrance();
6304
6305
			if ( ! $this->is_trial() ) {
6306
				return false;
6307
			}
6308
6309
			return $this->has_active_valid_license() && ( $this->_site->trial_plan_id == $this->_license->plan_id );
6310
		}
6311
6312
		/**
6313
		 * Check if trial already utilized.
6314
		 *
6315
		 * @since 1.0.9
6316
		 *
6317
		 * @return bool
6318
		 */
6319
		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...
6320
			$this->_logger->entrance();
6321
6322
			if ( ! $this->is_registered() ) {
6323
				return false;
6324
			}
6325
6326
			return $this->_site->is_trial_utilized();
6327
		}
6328
6329
		/**
6330
		 * Get trial plan information (if in trial).
6331
		 *
6332
		 * @author Vova Feldman (@svovaf)
6333
		 * @since  1.0.9
6334
		 *
6335
		 * @return bool|FS_Plugin_Plan
6336
		 */
6337
		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...
6338
			$this->_logger->entrance();
6339
6340
			if ( ! $this->is_trial() ) {
6341
				return false;
6342
			}
6343
6344
			return $this->_storage->trial_plan;
6345
		}
6346
6347
		/**
6348
		 * Check if the user has an activate, non-expired license on current plugin's install.
6349
		 *
6350
		 * @since 1.0.9
6351
		 *
6352
		 * @return bool
6353
		 */
6354
		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...
6355
			$this->_logger->entrance();
6356
6357
			if ( ! $this->is_registered() ) {
6358
				return false;
6359
			}
6360
6361
			if ( ! $this->has_paid_plan() ) {
6362
				return false;
6363
			}
6364
6365
			return (
6366
				! $this->is_trial() &&
6367
				'free' !== $this->_site->plan->name &&
6368
				$this->has_active_valid_license()
6369
			);
6370
		}
6371
6372
		/**
6373
		 * @author Vova Feldman (@svovaf)
6374
		 * @since  1.0.4
6375
		 *
6376
		 * @return bool
6377
		 */
6378
		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...
6379
			if ( ! $this->is_registered() ) {
6380
				return true;
6381
			}
6382
6383
			if ( ! $this->has_paid_plan() ) {
6384
				return true;
6385
			}
6386
6387
			return (
6388
				'free' === $this->_site->plan->name ||
6389
				! $this->has_features_enabled_license()
6390
			);
6391
		}
6392
6393
		/**
6394
		 * @author Vova Feldman (@svovaf)
6395
		 * @since  1.0.5
6396
		 *
6397
		 * @return bool
6398
		 */
6399
		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...
6400
			$this->_logger->entrance();
6401
6402
			$premium_license = $this->_get_available_premium_license();
6403
6404
			return ( false !== $premium_license );
6405
		}
6406
6407
		/**
6408
		 * Check if user has any licenses associated with the plugin (including expired or blocking).
6409
		 *
6410
		 * @author Vova Feldman (@svovaf)
6411
		 * @since  1.1.7.3
6412
		 *
6413
		 * @return bool
6414
		 */
6415
		private function has_any_license() {
6416
			return is_array( $this->_licenses ) && ( 0 < count( $this->_licenses ) );
6417
		}
6418
6419
		/**
6420
		 * @author Vova Feldman (@svovaf)
6421
		 * @since  1.0.5
6422
		 *
6423
		 * @return FS_Plugin_License|false
6424
		 */
6425
		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...
6426
			$this->_logger->entrance();
6427
6428
			if ( ! $this->has_paid_plan() ) {
6429
				return false;
6430
			}
6431
6432
			if ( is_array( $this->_licenses ) ) {
6433
				foreach ( $this->_licenses as $license ) {
6434
					if ( ! $license->is_utilized() && $license->is_features_enabled() ) {
6435
						return $license;
6436
					}
6437
				}
6438
			}
6439
6440
			return false;
6441
		}
6442
6443
		/**
6444
		 * Sync local plugin plans with remote server.
6445
		 *
6446
		 * @author Vova Feldman (@svovaf)
6447
		 * @since  1.0.5
6448
		 *
6449
		 * @return FS_Plugin_Plan[]|object
6450
		 */
6451
		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...
6452
			$plans = $this->_fetch_plugin_plans();
6453
6454
			if ( $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) {
6455
				$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...
6456
				$this->_store_plans();
6457
			}
6458
6459
			$this->do_action( 'after_plans_sync', $plans );
6460
6461
			return $this->_plans;
6462
		}
6463
6464
		/**
6465
		 * @author Vova Feldman (@svovaf)
6466
		 * @since  1.0.5
6467
		 *
6468
		 * @param number $id
6469
		 *
6470
		 * @return FS_Plugin_Plan|false
6471
		 */
6472
		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...
6473
			$this->_logger->entrance();
6474
6475
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
6476
				$this->_sync_plans();
6477
			}
6478
6479
			foreach ( $this->_plans as $plan ) {
6480
				if ( $id == $plan->id ) {
6481
					return $plan;
6482
				}
6483
			}
6484
6485
			return false;
6486
		}
6487
6488
		/**
6489
		 * @author Vova Feldman (@svovaf)
6490
		 * @since  1.1.8.1
6491
		 *
6492
		 * @param string $name
6493
		 *
6494
		 * @return FS_Plugin_Plan|false
6495
		 */
6496
		private function get_plan_by_name( $name ) {
6497
			$this->_logger->entrance();
6498
6499
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
6500
				$this->_sync_plans();
6501
			}
6502
6503
			foreach ( $this->_plans as $plan ) {
6504
				if ( $name == $plan->name ) {
6505
					return $plan;
6506
				}
6507
			}
6508
6509
			return false;
6510
		}
6511
6512
		/**
6513
		 * Sync local plugin plans with remote server.
6514
		 *
6515
		 * @author Vova Feldman (@svovaf)
6516
		 * @since  1.0.6
6517
		 *
6518
		 * @param number|bool $site_license_id
6519
		 *
6520
		 * @return FS_Plugin_License[]|object
6521
		 */
6522
		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...
6523
			$licenses = $this->_fetch_licenses( false, $site_license_id );
6524
6525
			if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) {
6526
				$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...
6527
				$this->_store_licenses();
6528
			}
6529
6530
			// Update current license.
6531
			if ( is_object( $this->_license ) ) {
6532
				$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...
6533
			}
6534
6535
			return $this->_licenses;
6536
		}
6537
6538
		/**
6539
		 * @author Vova Feldman (@svovaf)
6540
		 * @since  1.0.5
6541
		 *
6542
		 * @param number $id
6543
		 *
6544
		 * @return FS_Plugin_License|false
6545
		 */
6546
		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...
6547
			$this->_logger->entrance();
6548
6549
			if ( ! is_numeric( $id ) ) {
6550
				return false;
6551
			}
6552
6553
			if ( ! $this->has_any_license() ) {
6554
				$this->_sync_licenses();
6555
			}
6556
6557
			foreach ( $this->_licenses as $license ) {
6558
				if ( $id == $license->id ) {
6559
					return $license;
6560
				}
6561
			}
6562
6563
			return false;
6564
		}
6565
6566
		/**
6567
		 * Sync site's license with user licenses.
6568
		 *
6569
		 * @author Vova Feldman (@svovaf)
6570
		 * @since  1.0.6
6571
		 *
6572
		 * @param FS_Plugin_License|null $new_license
6573
		 */
6574
		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...
6575
			$this->_logger->entrance();
6576
6577
			$this->_license = $new_license;
6578
6579
			if ( ! is_object( $new_license ) ) {
6580
				$this->_site->license_id = null;
6581
				$this->_sync_site_subscription( null );
6582
6583
				return;
6584
			}
6585
6586
			$this->_site->license_id = $this->_license->id;
6587
6588
			if ( ! is_array( $this->_licenses ) ) {
6589
				$this->_licenses = array();
6590
			}
6591
6592
			$is_license_found = false;
6593
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
6594
				if ( $new_license->id == $this->_licenses[ $i ]->id ) {
6595
					$this->_licenses[ $i ] = $new_license;
6596
6597
					$is_license_found = true;
6598
					break;
6599
				}
6600
			}
6601
6602
			// If new license just append.
6603
			if ( ! $is_license_found ) {
6604
				$this->_licenses[] = $new_license;
6605
			}
6606
6607
			$this->_sync_site_subscription( $new_license );
6608
		}
6609
6610
		/**
6611
		 * Sync site's subscription.
6612
		 *
6613
		 * @author Vova Feldman (@svovaf)
6614
		 * @since  1.0.9
6615
		 *
6616
		 * @param FS_Plugin_License|null $license
6617
		 *
6618
		 * @return bool|\FS_Subscription
6619
		 */
6620
		private function _sync_site_subscription( $license ) {
6621
			if ( ! is_object( $license ) ) {
6622
				unset( $this->_storage->subscription );
6623
6624
				return false;
6625
			}
6626
6627
			// Load subscription details if not lifetime.
6628
			$subscription = $license->is_lifetime() ?
6629
				false :
6630
				$this->_fetch_site_license_subscription();
6631
6632
			if ( is_object( $subscription ) && ! isset( $subscription->error ) ) {
6633
				$this->_storage->subscription = $subscription;
6634
			} else {
6635
				unset( $this->_storage->subscription );
6636
			}
6637
6638
			return $subscription;
6639
		}
6640
6641
		/**
6642
		 * @author Vova Feldman (@svovaf)
6643
		 * @since  1.0.6
6644
		 *
6645
		 * @return bool|\FS_Plugin_License
6646
		 */
6647
		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...
6648
			return $this->_license;
6649
		}
6650
6651
		/**
6652
		 * @return bool|\FS_Subscription
6653
		 */
6654
		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...
6655
			return isset( $this->_storage->subscription ) ?
6656
				$this->_storage->subscription :
6657
				false;
6658
		}
6659
6660
		/**
6661
		 * @author Vova Feldman (@svovaf)
6662
		 * @since  1.0.2
6663
		 *
6664
		 * @param string $plan  Plan name
6665
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
6666
		 *
6667
		 * @return bool
6668
		 */
6669
		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...
6670
			$this->_logger->entrance();
6671
6672
			if ( ! $this->is_registered() ) {
6673
				return false;
6674
			}
6675
6676
			$plan = strtolower( $plan );
6677
6678
			if ( $this->_site->plan->name === $plan ) // Exact plan.
6679
			{
6680
				return true;
6681
			} else if ( $exact ) // Required exact, but plans are different.
6682
			{
6683
				return false;
6684
			}
6685
6686
			$current_plan_order  = - 1;
6687
			$required_plan_order = - 1;
6688
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
6689
				if ( $plan === $this->_plans[ $i ]->name ) {
6690
					$required_plan_order = $i;
6691
				} else if ( $this->_site->plan->name === $this->_plans[ $i ]->name ) {
6692
					$current_plan_order = $i;
6693
				}
6694
			}
6695
6696
			return ( $current_plan_order > $required_plan_order );
6697
		}
6698
6699
		/**
6700
		 * Check if module has only one plan.
6701
		 *
6702
		 * @author Vova Feldman (@svovaf)
6703
		 * @since  1.2.1.7
6704
		 *
6705
		 * @return bool
6706
		 */
6707
		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...
6708
			$this->_logger->entrance();
6709
6710
			if ( ! $this->is_registered() ||
6711
			     ! is_array( $this->_plans ) ||
6712
			     0 === count( $this->_plans )
6713
			) {
6714
				return true;
6715
			}
6716
6717
			return ( 1 === count( $this->_plans ) );
6718
		}
6719
6720
		/**
6721
		 * Check if plan based on trial. If not in trial mode, should return false.
6722
		 *
6723
		 * @since  1.0.9
6724
		 *
6725
		 * @param string $plan  Plan name
6726
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
6727
		 *
6728
		 * @return bool
6729
		 */
6730
		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...
6731
			$this->_logger->entrance();
6732
6733
			if ( ! $this->is_registered() ) {
6734
				return false;
6735
			}
6736
6737
			if ( ! $this->is_trial() ) {
6738
				return false;
6739
			}
6740
6741
			if ( ! isset( $this->_storage->trial_plan ) ) {
6742
				// Store trial plan information.
6743
				$this->_enrich_site_trial_plan( true );
6744
			}
6745
6746
			if ( $this->_storage->trial_plan->name === $plan ) // Exact plan.
6747
			{
6748
				return true;
6749
			} else if ( $exact ) // Required exact, but plans are different.
6750
			{
6751
				return false;
6752
			}
6753
6754
			$current_plan_order  = - 1;
6755
			$required_plan_order = - 1;
6756
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
6757
				if ( $plan === $this->_plans[ $i ]->name ) {
6758
					$required_plan_order = $i;
6759
				} else if ( $this->_storage->trial_plan->name === $this->_plans[ $i ]->name ) {
6760
					$current_plan_order = $i;
6761
				}
6762
			}
6763
6764
			return ( $current_plan_order > $required_plan_order );
6765
		}
6766
6767
		/**
6768
		 * Check if plugin has any paid plans.
6769
		 *
6770
		 * @author Vova Feldman (@svovaf)
6771
		 * @since  1.0.7
6772
		 *
6773
		 * @return bool
6774
		 */
6775
		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...
6776
			return $this->_has_paid_plans ||
6777
			       FS_Plan_Manager::instance()->has_paid_plan( $this->_plans );
6778
		}
6779
6780
		/**
6781
		 * Check if plugin has any plan with a trail.
6782
		 *
6783
		 * @author Vova Feldman (@svovaf)
6784
		 * @since  1.0.9
6785
		 *
6786
		 * @return bool
6787
		 */
6788
		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...
6789
			if ( ! $this->is_registered() ) {
6790
				/**
6791
				 * @author Vova Feldman(@svovaf)
6792
				 * @since  1.2.1.5
6793
				 *
6794
				 * Allow setting a trial from the SDK without calling the API.
6795
				 * But, if the user did opt-in, continue using the real data from the API.
6796
				 */
6797
				if ( $this->_trial_days >= 0 ) {
6798
					return true;
6799
				}
6800
6801
				return false;
6802
			}
6803
6804
			return $this->_storage->get( 'has_trial_plan', false );
6805
		}
6806
6807
		/**
6808
		 * Check if plugin has any free plan, or is it premium only.
6809
		 *
6810
		 * Note: If no plans configured, assume plugin is free.
6811
		 *
6812
		 * @author Vova Feldman (@svovaf)
6813
		 * @since  1.0.7
6814
		 *
6815
		 * @return bool
6816
		 */
6817
		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...
6818
			return ! $this->is_only_premium();
6819
		}
6820
6821
		/**
6822
		 * Displays a license activation dialog box when the user clicks on the "Activate License"
6823
		 * or "Change License" link on the plugins
6824
		 * page.
6825
		 *
6826
		 * @author Leo Fajardo (@leorw)
6827
		 * @since  1.1.9
6828
		 */
6829
		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...
6830
			$vars = array(
6831
				'id' => $this->_module_id,
6832
			);
6833
6834
			fs_require_template( 'forms/license-activation.php', $vars );
6835
			fs_require_template( 'forms/resend-key.php', $vars );
6836
		}
6837
6838
		/**
6839
		 * Displays the opt-out dialog box when the user clicks on the "Opt Out" link on the "Plugins"
6840
		 * page.
6841
		 *
6842
		 * @author Leo Fajardo (@leorw)
6843
		 * @since  1.2.1.5
6844
		 */
6845
		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...
6846
			if ( $this->is_theme() ) {
6847
				$vars = null;
6848
				fs_require_once_template( '/js/jquery.content-change.php', $vars );
6849
			}
6850
6851
			$vars = array( 'id' => $this->_module_id );
6852
			fs_require_template( 'forms/optout.php', $vars );
6853
		}
6854
6855
		/**
6856
		 * Prepare page to include all required UI and logic for the license activation dialog.
6857
		 *
6858
		 * @author Vova Feldman (@svovaf)
6859
		 * @since  1.2.0
6860
		 */
6861
		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...
6862
			if ( ! $this->is_user_admin() ) {
6863
				// Only admins can activate a license.
6864
				return;
6865
			}
6866
6867
			if ( ! $this->has_paid_plan() ) {
6868
				// Module doesn't have any paid plans.
6869
				return;
6870
			}
6871
6872
			if ( ! $this->is_premium() ) {
6873
				// Only add license activation logic to the premium version.
6874
				return;
6875
			}
6876
6877
			// Add license activation link and AJAX request handler.
6878
			if ( self::is_plugins_page() ) {
6879
				/**
6880
				 * @since 1.2.0 Add license action link only on plugins page.
6881
				 */
6882
				$this->_add_license_action_link();
6883
			}
6884
6885
			// Add license activation AJAX callback.
6886
			$this->add_ajax_action( 'activate_license', array( &$this, '_activate_license_ajax_action' ) );
6887
6888
			// Add resend license AJAX callback.
6889
			$this->add_ajax_action( 'resend_license_key', array( &$this, '_resend_license_key_ajax_action' ) );
6890
		}
6891
6892
		/**
6893
		 * @author Leo Fajardo (@leorw)
6894
		 * @since  1.1.9
6895
		 */
6896
		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...
6897
			$this->_logger->entrance();
6898
6899
			$this->check_ajax_referer( 'activate_license' );
6900
6901
			$license_key = trim( fs_request_get( 'license_key' ) );
6902
6903
			if ( empty( $license_key ) ) {
6904
				exit;
6905
			}
6906
6907
			$plugin_id = fs_request_get( 'module_id', '', 'post' );
6908
			$fs        = ( $plugin_id == $this->_module_id ) ?
6909
				$this :
6910
				$this->get_addon_instance( $plugin_id );
6911
6912
			$error     = false;
6913
			$next_page = false;
6914
6915
			if ( $fs->is_registered() ) {
6916
				$api     = $fs->get_api_site_scope();
6917
				$install = $api->call( '/', 'put', array(
6918
					'license_key' => $fs->apply_filters( 'license_key', $license_key )
6919
				) );
6920
6921
				if ( isset( $install->error ) ) {
6922
					$error = $install->error->message;
6923
				} else {
6924
					$parent_fs = $fs->is_addon() ?
6925
						$fs->get_parent_instance() :
6926
						$fs;
6927
6928
					$next_page = $parent_fs->_get_sync_license_url( $fs->get_id(), true );
6929
6930
					$fs->reconnect_locally();
6931
				}
6932
			} else {
6933
				$next_page = $fs->opt_in( false, false, false, $license_key );
6934
6935
				if ( isset( $next_page->error ) ) {
6936
					$error = $next_page->error;
6937
				}
6938
			}
6939
6940
			$result = array(
6941
				'success' => ( false === $error )
6942
			);
6943
6944
			if ( false !== $error ) {
6945
				$result['error'] = $error;
6946
			} else {
6947
				$result['next_page'] = $next_page;
6948
			}
6949
6950
			echo json_encode( $result );
6951
6952
			exit;
6953
		}
6954
6955
		/**
6956
		 * Billing update AJAX callback.
6957
		 *
6958
		 * @author Vova Feldman (@svovaf)
6959
		 * @since  1.2.1.5
6960
		 */
6961
		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...
6962
			$this->_logger->entrance();
6963
6964
			$this->check_ajax_referer( 'update_billing' );
6965
6966
			if ( ! $this->is_user_admin() ) {
6967
				// Only for admins.
6968
				self::shoot_ajax_failure();
6969
			}
6970
6971
			$billing = fs_request_get( 'billing' );
6972
6973
			$api    = $this->get_api_user_scope();
6974
			$result = $api->call( '/billing.json', 'put', array_merge( $billing, array(
6975
				'plugin_id' => $this->get_parent_id(),
6976
			) ) );
6977
6978
			if ( ! $this->is_api_result_entity( $result ) ) {
6979
				self::shoot_ajax_failure();
6980
			}
6981
6982
			// Purge cached billing.
6983
			$this->get_api_user_scope()->purge_cache( 'billing.json' );
6984
6985
			self::shoot_ajax_success();
6986
		}
6987
6988
		/**
6989
		 * Trial start for anonymous users (AJAX callback).
6990
		 *
6991
		 * @author Vova Feldman (@svovaf)
6992
		 * @since  1.2.1.5
6993
		 */
6994
		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...
6995
			$this->_logger->entrance();
6996
6997
			$this->check_ajax_referer( 'start_trial' );
6998
6999
			if ( ! $this->is_user_admin() ) {
7000
				// Only for admins.
7001
				self::shoot_ajax_failure();
7002
			}
7003
7004
			$trial_data = fs_request_get( 'trial' );
7005
7006
			$next_page = $this->opt_in(
7007
				false,
7008
				false,
7009
				false,
7010
				false,
7011
				false,
7012
				$trial_data['plan_id']
7013
			);
7014
7015
			if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) {
7016
				self::shoot_ajax_failure(
7017
					isset( $next_page->error ) ?
7018
						$next_page->error->message :
7019
						var_export( $next_page, true )
7020
				);
7021
			}
7022
7023
			$this->shoot_ajax_success( array(
7024
				'next_page' => $next_page,
7025
			) );
7026
		}
7027
7028
		/**
7029
		 * @author Leo Fajardo (@leorw)
7030
		 * @since  1.2.0
7031
		 */
7032
		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...
7033
			$this->_logger->entrance();
7034
7035
			$this->check_ajax_referer( 'resend_license_key' );
7036
7037
			$email_address = sanitize_email( trim( fs_request_get( 'email', '', 'post' ) ) );
7038
7039
			if ( empty( $email_address ) ) {
7040
				exit;
7041
			}
7042
7043
			$error = false;
7044
7045
			$api    = $this->get_api_plugin_scope();
7046
			$result = $api->call( '/licenses/resend.json', 'post',
7047
				array(
7048
					'email' => $email_address,
7049
					'url'   => home_url(),
7050
				)
7051
			);
7052
7053
			if ( is_object( $result ) && isset( $result->error ) ) {
7054
				$error = $result->error;
7055
7056
				if ( in_array( $error->code, array( 'invalid_email', 'no_user' ) ) ) {
7057
					$error = $this->get_text( 'email-not-found' );
7058
				} else if ( 'no_license' === $error->code ) {
7059
					$error = $this->get_text( 'no-active-licenses' );
7060
				} else {
7061
					$error = $error->message;
7062
				}
7063
			}
7064
7065
			$licenses = array(
7066
				'success' => ( false === $error )
7067
			);
7068
7069
			if ( false !== $error ) {
7070
				$licenses['error'] = sprintf( '%s... %s', $this->get_text( 'oops' ), strtolower( $error ) );
7071
			}
7072
7073
			echo json_encode( $licenses );
7074
7075
			exit;
7076
		}
7077
7078
		/**
7079
		 * @author Vova Feldman (@svovaf)
7080
		 * @since  1.2.1.8
7081
		 *
7082
		 * @var string
7083
		 */
7084
		private static $_pagenow;
7085
7086
		/**
7087
		 * Get current page or the referer if executing a WP AJAX request.
7088
		 *
7089
		 * @author Vova Feldman (@svovaf)
7090
		 * @since  1.2.1.8
7091
		 *
7092
		 * @return string
7093
		 */
7094
		static function get_current_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7095
			if ( ! isset( self::$_pagenow ) ) {
7096
				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...
7097
7098
				self::$_pagenow = $pagenow;
7099
7100
				if ( self::is_ajax() &&
7101
				     'admin-ajax.php' === $pagenow
7102
				) {
7103
					$referer = wp_get_raw_referer();
7104
7105
					if ( is_string( $referer ) ) {
7106
						$parts = explode( '?', $referer );
7107
7108
						self::$_pagenow = basename( $parts[0] );
7109
					}
7110
				}
7111
			}
7112
7113
			return self::$_pagenow;
7114
		}
7115
7116
		/**
7117
		 * Helper method to check if user in the plugins page.
7118
		 *
7119
		 * @author Vova Feldman (@svovaf)
7120
		 * @since  1.2.1.5
7121
		 *
7122
		 * @return bool
7123
		 */
7124
		static function is_plugins_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7125
			return ( 'plugins.php' === self::get_current_page() );
7126
		}
7127
7128
		/**
7129
		 * Helper method to check if user in the themes page.
7130
		 *
7131
		 * @author Vova Feldman (@svovaf)
7132
		 * @since  1.2.2.6
7133
		 *
7134
		 * @return bool
7135
		 */
7136
		static function is_themes_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7137
			return ( 'themes.php' === self::get_current_page() );
7138
		}
7139
7140
		#----------------------------------------------------------------------------------
7141
		#region URL Generators
7142
		#----------------------------------------------------------------------------------
7143
7144
		/**
7145
		 * Alias to pricing_url().
7146
		 *
7147
		 * @author Vova Feldman (@svovaf)
7148
		 * @since  1.0.2
7149
		 *
7150
		 * @uses   pricing_url()
7151
		 *
7152
		 * @param string $period Billing cycle
7153
		 * @param bool   $is_trial
7154
		 *
7155
		 * @return string
7156
		 */
7157
		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...
7158
			return $this->pricing_url( $period, $is_trial );
7159
		}
7160
7161
		/**
7162
		 * @author Vova Feldman (@svovaf)
7163
		 * @since  1.0.9
7164
		 *
7165
		 * @uses   get_upgrade_url()
7166
		 *
7167
		 * @return string
7168
		 */
7169
		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...
7170
			return $this->get_upgrade_url( WP_FS__PERIOD_ANNUALLY, true );
7171
		}
7172
7173
		/**
7174
		 * Plugin's pricing URL.
7175
		 *
7176
		 * @author Vova Feldman (@svovaf)
7177
		 * @since  1.0.4
7178
		 *
7179
		 * @param string $billing_cycle Billing cycle
7180
		 *
7181
		 * @param bool   $is_trial
7182
		 *
7183
		 * @return string
7184
		 */
7185
		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...
7186
			$this->_logger->entrance();
7187
7188
			$params = array(
7189
				'billing_cycle' => $billing_cycle
7190
			);
7191
7192
			if ( $is_trial ) {
7193
				$params['trial'] = 'true';
7194
			}
7195
7196
			return $this->_get_admin_page_url( 'pricing', $params );
7197
		}
7198
7199
		/**
7200
		 * Checkout page URL.
7201
		 *
7202
		 * @author   Vova Feldman (@svovaf)
7203
		 * @since    1.0.6
7204
		 *
7205
		 * @param string $billing_cycle Billing cycle
7206
		 * @param bool   $is_trial
7207
		 * @param array  $extra         (optional) Extra parameters, override other query params.
7208
		 *
7209
		 * @return string
7210
		 */
7211
		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...
7212
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
7213
			$is_trial = false,
7214
			$extra = array()
7215
		) {
7216
			$this->_logger->entrance();
7217
7218
			$params = array(
7219
				'checkout'      => 'true',
7220
				'billing_cycle' => $billing_cycle,
7221
			);
7222
7223
			if ( $is_trial ) {
7224
				$params['trial'] = 'true';
7225
			}
7226
7227
			/**
7228
			 * Params in extra override other params.
7229
			 */
7230
			$params = array_merge( $params, $extra );
7231
7232
			return $this->_get_admin_page_url( 'pricing', $params );
7233
		}
7234
7235
		/**
7236
		 * Add-on checkout URL.
7237
		 *
7238
		 * @author   Vova Feldman (@svovaf)
7239
		 * @since    1.1.7
7240
		 *
7241
		 * @param number $addon_id
7242
		 * @param number $pricing_id
7243
		 * @param string $billing_cycle
7244
		 * @param bool   $is_trial
7245
		 *
7246
		 * @return string
7247
		 */
7248
		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...
7249
			$addon_id,
7250
			$pricing_id,
7251
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
7252
			$is_trial = false
7253
		) {
7254
			return $this->checkout_url( $billing_cycle, $is_trial, array(
7255
				'plugin_id'  => $addon_id,
7256
				'pricing_id' => $pricing_id,
7257
			) );
7258
		}
7259
7260
		#endregion
7261
7262
		#endregion ------------------------------------------------------------------
7263
7264
		/**
7265
		 * Check if plugin has any add-ons.
7266
		 *
7267
		 * @author Vova Feldman (@svovaf)
7268
		 * @since  1.0.5
7269
		 *
7270
		 * @since  1.1.7.3 Base logic only on the parameter provided by the developer in the init function.
7271
		 *
7272
		 * @return bool
7273
		 */
7274
		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...
7275
			$this->_logger->entrance();
7276
7277
			return $this->_has_addons;
7278
		}
7279
7280
		/**
7281
		 * Check if plugin can work in anonymous mode.
7282
		 *
7283
		 * @author     Vova Feldman (@svovaf)
7284
		 * @since      1.0.9
7285
		 *
7286
		 * @return bool
7287
		 *
7288
		 * @deprecated Please use is_enable_anonymous() instead
7289
		 */
7290
		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...
7291
			return $this->_enable_anonymous;
7292
		}
7293
7294
		/**
7295
		 * Check if plugin can work in anonymous mode.
7296
		 *
7297
		 * @author Vova Feldman (@svovaf)
7298
		 * @since  1.1.9
7299
		 *
7300
		 * @return bool
7301
		 */
7302
		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...
7303
			return $this->_enable_anonymous;
7304
		}
7305
7306
		/**
7307
		 * Check if plugin is premium only (no free plans).
7308
		 *
7309
		 * @author Vova Feldman (@svovaf)
7310
		 * @since  1.1.9
7311
		 *
7312
		 * @return bool
7313
		 */
7314
		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...
7315
			return $this->_is_premium_only;
7316
		}
7317
7318
		/**
7319
		 * Checks if the plugin's type is "plugin". The other type is "theme".
7320
		 *
7321
		 * @author Leo Fajardo (@leorw)
7322
		 * @since  1.2.2
7323
		 *
7324
		 * @return bool
7325
		 */
7326
		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...
7327
			return ( WP_FS__MODULE_TYPE_PLUGIN === $this->_module_type );
7328
		}
7329
7330
		/**
7331
		 * @author Leo Fajardo (@leorw)
7332
		 * @since  1.2.2
7333
		 *
7334
		 * @return string
7335
		 */
7336
		function get_module_type() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7337
			if ( ! isset( $this->_module_type ) ) {
7338
				$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() );
7339
				$this->_module_type    = $id_slug_type_path_map[ $this->_module_id ]['type'];
7340
			}
7341
7342
			return $this->_module_type;
7343
		}
7344
7345
		/**
7346
		 * @author Leo Fajardo (@leorw)
7347
		 * @since  1.2.2
7348
		 *
7349
		 * @return string
7350
		 */
7351
		function get_plugin_main_file_path() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7352
			return $this->_plugin_main_file_path;
7353
		}
7354
7355
		/**
7356
		 * Check if module has a premium code version.
7357
		 *
7358
		 * Serviceware module might be freemium without any
7359
		 * premium code version, where the paid features
7360
		 * are all part of the service.
7361
		 *
7362
		 * @author Vova Feldman (@svovaf)
7363
		 * @since  1.2.1.6
7364
		 *
7365
		 * @return bool
7366
		 */
7367
		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...
7368
			return $this->_has_premium_version;
7369
		}
7370
7371
		/**
7372
		 * Check if feature supported with current site's plan.
7373
		 *
7374
		 * @author Vova Feldman (@svovaf)
7375
		 * @since  1.0.1
7376
		 *
7377
		 * @todo   IMPLEMENT
7378
		 *
7379
		 * @param number $feature_id
7380
		 *
7381
		 * @throws Exception
7382
		 */
7383
		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...
7384
			throw new Exception( 'not implemented' );
7385
		}
7386
7387
		/**
7388
		 * @author Vova Feldman (@svovaf)
7389
		 * @since  1.0.1
7390
		 *
7391
		 * @return bool Is running in SSL/HTTPS
7392
		 */
7393
		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...
7394
			return WP_FS__IS_HTTPS;
7395
		}
7396
7397
		/**
7398
		 * @author Vova Feldman (@svovaf)
7399
		 * @since  1.0.9
7400
		 *
7401
		 * @return bool Is running in AJAX call.
7402
		 *
7403
		 * @link   http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax
7404
		 */
7405
		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...
7406
			return ( defined( 'DOING_AJAX' ) && DOING_AJAX );
7407
		}
7408
7409
		/**
7410
		 * Check if it's an AJAX call targeted for the current module.
7411
		 *
7412
		 * @author Vova Feldman (@svovaf)
7413
		 * @since  1.2.0
7414
		 *
7415
		 * @param array|string $actions Collection of AJAX actions.
7416
		 *
7417
		 * @return bool
7418
		 */
7419
		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...
7420
			// Verify it's an ajax call.
7421
			if ( ! self::is_ajax() ) {
7422
				return false;
7423
			}
7424
7425
			// Verify the call is relevant for the plugin.
7426
			if ( $this->_module_id != fs_request_get( 'module_id' ) ) {
7427
				return false;
7428
			}
7429
7430
			// Verify it's one of the specified actions.
7431
			if ( is_string( $actions ) ) {
7432
				$actions = explode( ',', $actions );
7433
			}
7434
7435
			if ( is_array( $actions ) && 0 < count( $actions ) ) {
7436
				$ajax_action = fs_request_get( 'action' );
7437
7438
				foreach ( $actions as $action ) {
7439
					if ( $ajax_action === $this->get_action_tag( $action ) ) {
7440
						return true;
7441
					}
7442
				}
7443
			}
7444
7445
			return false;
7446
		}
7447
7448
		/**
7449
		 * Check if it's an AJAX call targeted for current request.
7450
		 *
7451
		 * @author Vova Feldman (@svovaf)
7452
		 * @since  1.2.0
7453
		 *
7454
		 * @param array|string $actions Collection of AJAX actions.
7455
		 * @param number|null  $module_id
7456
		 *
7457
		 * @return bool
7458
		 */
7459
		static function is_ajax_action_static( $actions, $module_id = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7460
			// Verify it's an ajax call.
7461
			if ( ! self::is_ajax() ) {
7462
				return false;
7463
			}
7464
7465
7466
			if ( ! empty( $module_id ) ) {
7467
				// Verify the call is relevant for the plugin.
7468
				if ( $module_id != fs_request_get( 'module_id' ) ) {
7469
					return false;
7470
				}
7471
			}
7472
7473
			// Verify it's one of the specified actions.
7474
			if ( is_string( $actions ) ) {
7475
				$actions = explode( ',', $actions );
7476
			}
7477
7478
			if ( is_array( $actions ) && 0 < count( $actions ) ) {
7479
				$ajax_action = fs_request_get( 'action' );
7480
7481
				foreach ( $actions as $action ) {
7482
					if ( $ajax_action === self::get_ajax_action_static( $action, $module_id ) ) {
7483
						return true;
7484
					}
7485
				}
7486
			}
7487
7488
			return false;
7489
		}
7490
7491
		/**
7492
		 * @author Vova Feldman (@svovaf)
7493
		 * @since  1.1.7
7494
		 *
7495
		 * @return bool
7496
		 */
7497
		static function is_cron() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7498
			return ( defined( 'DOING_CRON' ) && DOING_CRON );
7499
		}
7500
7501
		/**
7502
		 * Check if a real user is visiting the admin dashboard.
7503
		 *
7504
		 * @author Vova Feldman (@svovaf)
7505
		 * @since  1.1.7
7506
		 *
7507
		 * @return bool
7508
		 */
7509
		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...
7510
			return is_admin() && ! self::is_ajax() && ! self::is_cron();
7511
		}
7512
7513
		/**
7514
		 * Check if a real user is in the customizer view.
7515
		 *
7516
		 * @author Vova Feldman (@svovaf)
7517
		 * @since  1.2.2.7
7518
		 *
7519
		 * @return bool
7520
		 */
7521
		static function is_customizer() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7522
			return is_customize_preview();
7523
		}
7524
7525
		/**
7526
		 * Check if running in HTTPS and if site's plan matching the specified plan.
7527
		 *
7528
		 * @param string $plan
7529
		 * @param bool   $exact
7530
		 *
7531
		 * @return bool
7532
		 */
7533
		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...
7534
			return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) );
7535
		}
7536
7537
		/**
7538
		 * Construct plugin's settings page URL.
7539
		 *
7540
		 * @author Vova Feldman (@svovaf)
7541
		 * @since  1.0.4
7542
		 *
7543
		 * @param string $page
7544
		 * @param array  $params
7545
		 *
7546
		 * @return string
7547
		 */
7548
		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...
7549
			if ( 0 < count( $params ) ) {
7550
				foreach ( $params as $k => $v ) {
7551
					$params[ $k ] = urlencode( $v );
7552
				}
7553
			}
7554
7555
			$page_param = $this->_menu->get_slug( $page );
7556
7557
            if ( empty( $page ) &&
7558
                $this->is_theme() &&
7559
                // Show the opt-in as an overlay for free wp.org themes or themes without any settings page.
7560
                ( $this->is_free_wp_org_theme() || ! $this->has_settings_menu() ) ) {
7561
                    $params[ $this->get_unique_affix() . '_show_optin' ] = 'true';
7562
7563
                    return add_query_arg(
7564
                        $params,
7565
                        admin_url( 'themes.php' )
7566
                    );
7567
            }
7568
7569
			if ( ! $this->has_settings_menu() ) {
7570
				if ( ! empty( $page ) ) {
7571
					// Module doesn't have a setting page, but since the request is for
7572
					// a specific Freemius page, use the admin.php path.
7573
					return add_query_arg( array_merge( $params, array(
7574
						'page' => $page_param,
7575
					) ), admin_url( 'admin.php' ) );
7576
				} else {
7577
					if ( $this->is_activation_mode() ) {
7578
						/**
7579
						 * @author Vova Feldman
7580
						 * @since  1.2.1.6
7581
						 *
7582
						 * If plugin doesn't have a settings page, create one for the opt-in screen.
7583
						 */
7584
						return add_query_arg( array_merge( $params, array(
7585
							'page' => $this->_slug,
7586
						) ), admin_url( 'admin.php', 'admin' ) );
7587
					} else {
7588
						// Plugin without a settings page.
7589
						return admin_url( 'plugins.php' );
7590
					}
7591
				}
7592
			}
7593
7594
			// Module has a submenu settings page.
7595
			if ( ! $this->_menu->is_top_level() ) {
7596
				$parent_slug = $this->_menu->get_parent_slug();
7597
				$menu_file   = ( false !== strpos( $parent_slug, '.php' ) ) ?
7598
					$parent_slug :
7599
					'admin.php';
7600
7601
				return add_query_arg( array_merge( $params, array(
7602
					'page' => $page_param,
7603
				) ), admin_url( $menu_file, 'admin' ) );
7604
			}
7605
7606
			// Module has a top level CPT settings page.
7607
			if ( $this->_menu->is_cpt() ) {
7608
				if ( empty( $page ) && $this->is_activation_mode() ) {
7609
					return add_query_arg( array_merge( $params, array(
7610
						'page' => $page_param
7611
					) ), admin_url( 'admin.php', 'admin' ) );
7612
				} else {
7613
					if ( ! empty( $page ) ) {
7614
						$params['page'] = $page_param;
7615
					}
7616
7617
					return add_query_arg(
7618
						$params,
7619
						admin_url( $this->_menu->get_raw_slug(), 'admin' )
7620
					);
7621
				}
7622
			}
7623
7624
			// Module has a custom top level settings page.
7625
			return add_query_arg( array_merge( $params, array(
7626
				'page' => $page_param,
7627
			) ), admin_url( 'admin.php', 'admin' ) );
7628
		}
7629
7630
		/**
7631
		 * Check if currently in a specified admin page.
7632
		 *
7633
		 * @author Vova Feldman (@svovaf)
7634
		 * @since  1.2.2.7
7635
		 *
7636
		 * @param string $page
7637
		 *
7638
		 * @return bool
7639
		 */
7640
		function is_admin_page( $page ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7641
			return ( $this->_menu->get_slug( $page ) === fs_request_get( 'page', '', 'get' ) );
7642
		}
7643
7644
		/**
7645
		 * Get module's main admin setting page URL.
7646
		 *
7647
		 * @author Vova Feldman (@svovaf)
7648
		 * @since  1.2.2.7
7649
		 *
7650
		 * @return string
7651
		 */
7652
		function main_menu_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7653
			return $this->_menu->main_menu_url();
7654
		}
7655
7656
		/**
7657
		 * Check if currently on the theme's setting page or
7658
		 * on any of the Freemius added pages (via tabs).
7659
		 *
7660
		 * @author Vova Feldman (@svovaf)
7661
		 * @since  1.2.2.7
7662
		 *
7663
		 * @return bool
7664
		 */
7665
		function is_theme_settings_page() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7666
			return fs_starts_with(
7667
				fs_request_get( 'page', '', 'get' ),
7668
				$this->_menu->get_slug()
7669
			);
7670
		}
7671
7672
		/**
7673
		 * Plugin's account page + sync license URL.
7674
		 *
7675
		 * @author Vova Feldman (@svovaf)
7676
		 * @since  1.1.9.1
7677
		 *
7678
		 * @param bool|number $plugin_id
7679
		 * @param bool        $add_action_nonce
7680
		 * @param array       $params
7681
		 *
7682
		 * @return string
7683
		 */
7684
		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...
7685
			if ( is_numeric( $plugin_id ) ) {
7686
				$params['plugin_id'] = $plugin_id;
7687
			}
7688
7689
			return $this->get_account_url(
7690
				$this->get_unique_affix() . '_sync_license',
7691
				$params,
7692
				$add_action_nonce
7693
			);
7694
		}
7695
7696
		/**
7697
		 * Plugin's account URL.
7698
		 *
7699
		 * @author Vova Feldman (@svovaf)
7700
		 * @since  1.0.4
7701
		 *
7702
		 * @param bool|string $action
7703
		 * @param array       $params
7704
		 *
7705
		 * @param bool        $add_action_nonce
7706
		 *
7707
		 * @return string
7708
		 */
7709
		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...
7710
			if ( is_string( $action ) ) {
7711
				$params['fs_action'] = $action;
7712
			}
7713
7714
			self::require_pluggable_essentials();
7715
7716
			return ( $add_action_nonce && is_string( $action ) ) ?
7717
				fs_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) :
7718
				$this->_get_admin_page_url( 'account', $params );
7719
		}
7720
7721
		/**
7722
		 * @author  Vova Feldman (@svovaf)
7723
		 * @since   1.2.0
7724
		 *
7725
		 * @param string $tab
7726
		 * @param bool   $action
7727
		 * @param array  $params
7728
		 * @param bool   $add_action_nonce
7729
		 *
7730
		 * @return string
7731
		 *
7732
		 * @uses    get_account_url()
7733
		 */
7734
		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...
7735
			$params['tab'] = $tab;
7736
7737
			return $this->get_account_url( $action, $params, $add_action_nonce );
7738
		}
7739
7740
		/**
7741
		 * Plugin's account URL.
7742
		 *
7743
		 * @author Vova Feldman (@svovaf)
7744
		 * @since  1.0.4
7745
		 *
7746
		 * @param bool|string $topic
7747
		 * @param bool|string $message
7748
		 *
7749
		 * @return string
7750
		 */
7751
		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...
7752
			$params = array();
7753
			if ( is_string( $topic ) ) {
7754
				$params['topic'] = $topic;
7755
			}
7756
			if ( is_string( $message ) ) {
7757
				$params['message'] = $message;
7758
			}
7759
7760
			if ( $this->is_addon() ) {
7761
				$params['addon_id'] = $this->get_id();
7762
7763
				return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params );
7764
			} else {
7765
				return $this->_get_admin_page_url( 'contact', $params );
7766
			}
7767
		}
7768
7769
		/**
7770
		 * Add-on direct info URL.
7771
		 *
7772
		 * @author Vova Feldman (@svovaf)
7773
		 * @since  1.1.0
7774
		 *
7775
		 * @param string $slug
7776
		 *
7777
		 * @return string
7778
		 */
7779
		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...
7780
			return $this->_get_admin_page_url( 'addons', array(
7781
				'slug' => $slug
7782
			) );
7783
		}
7784
7785
		/* Logger
7786
		------------------------------------------------------------------------------------------------------------------*/
7787
		/**
7788
		 * @param string $id
7789
		 * @param bool   $prefix_slug
7790
		 *
7791
		 * @return FS_Logger
7792
		 */
7793
		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...
7794
			return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id );
7795
		}
7796
7797
		/**
7798
		 * @param      $id
7799
		 * @param bool $load_options
7800
		 * @param bool $prefix_slug
7801
		 *
7802
		 * @return FS_Option_Manager
7803
		 */
7804
		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...
7805
			return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options );
7806
		}
7807
7808
		/* Security
7809
		------------------------------------------------------------------------------------------------------------------*/
7810
		private static function _encrypt( $str ) {
7811
			if ( is_null( $str ) ) {
7812
				return null;
7813
			}
7814
7815
			/**
7816
			 * The encrypt/decrypt functions are used to protect
7817
			 * the user from messing up with some of the sensitive
7818
			 * data stored for the module as a JSON in the database.
7819
			 *
7820
			 * I used the same suggested hack by the theme review team.
7821
			 * For more details, look at the function `Base64UrlDecode()`
7822
			 * in `./sdk/FreemiusBase.php`.
7823
			 *
7824
			 * @todo   Remove this hack once the base64 error is removed from the Theme Check.
7825
			 *
7826
			 * @author Vova Feldman (@svovaf)
7827
			 * @since  1.2.2
7828
			 */
7829
			$fn = 'base64' . '_encode';
7830
7831
			return $fn( $str );
7832
		}
7833
7834
		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...
7835
			if ( is_null( $str ) ) {
7836
				return null;
7837
			}
7838
7839
			/**
7840
			 * The encrypt/decrypt functions are used to protect
7841
			 * the user from messing up with some of the sensitive
7842
			 * data stored for the module as a JSON in the database.
7843
			 *
7844
			 * I used the same suggested hack by the theme review team.
7845
			 * For more details, look at the function `Base64UrlDecode()`
7846
			 * in `./sdk/FreemiusBase.php`.
7847
			 *
7848
			 * @todo   Remove this hack once the base64 error is removed from the Theme Check.
7849
			 *
7850
			 * @author Vova Feldman (@svovaf)
7851
			 * @since  1.2.2
7852
			 */
7853
			$fn = 'base64' . '_decode';
7854
7855
			return $fn( $str );
7856
		}
7857
7858
		/**
7859
		 * @author Vova Feldman (@svovaf)
7860
		 * @since  1.0.5
7861
		 *
7862
		 * @param FS_Entity $entity
7863
		 *
7864
		 * @return FS_Entity Return an encrypted clone entity.
7865
		 */
7866
		private static function _encrypt_entity( FS_Entity $entity ) {
7867
			$clone = clone $entity;
7868
			$props = get_object_vars( $entity );
7869
7870
			foreach ( $props as $key => $val ) {
7871
				$clone->{$key} = self::_encrypt( $val );
7872
			}
7873
7874
			return $clone;
7875
		}
7876
7877
		/**
7878
		 * @author Vova Feldman (@svovaf)
7879
		 * @since  1.0.5
7880
		 *
7881
		 * @param FS_Entity $entity
7882
		 *
7883
		 * @return FS_Entity Return an decrypted clone entity.
7884
		 */
7885
		private static function decrypt_entity( FS_Entity $entity ) {
7886
			$clone = clone $entity;
7887
			$props = get_object_vars( $entity );
7888
7889
			foreach ( $props as $key => $val ) {
7890
				$clone->{$key} = self::_decrypt( $val );
7891
			}
7892
7893
			return $clone;
7894
		}
7895
7896
		/**
7897
		 * Tries to activate account based on POST params.
7898
		 *
7899
		 * @author Vova Feldman (@svovaf)
7900
		 * @since  1.0.2
7901
		 */
7902
		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...
7903
			if ( $this->is_registered() ) {
7904
				// Already activated.
7905
				return;
7906
			}
7907
7908
			self::_clean_admin_content_section();
7909
7910
			if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) {
7911
//				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...
7912
7913
				// Verify matching plugin details.
7914
				if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) {
7915
					return;
7916
				}
7917
7918
				$user              = new FS_User();
7919
				$user->id          = fs_request_get( 'user_id' );
7920
				$user->public_key  = fs_request_get( 'user_public_key' );
7921
				$user->secret_key  = fs_request_get( 'user_secret_key' );
7922
				$user->email       = fs_request_get( 'user_email' );
7923
				$user->first       = fs_request_get( 'user_first' );
7924
				$user->last        = fs_request_get( 'user_last' );
7925
				$user->is_verified = fs_request_get_bool( 'user_is_verified' );
7926
7927
				$site              = new FS_Site();
7928
				$site->id          = fs_request_get( 'install_id' );
7929
				$site->public_key  = fs_request_get( 'install_public_key' );
7930
				$site->secret_key  = fs_request_get( 'install_secret_key' );
7931
				$site->plan->id    = fs_request_get( 'plan_id' );
7932
				$site->plan->title = fs_request_get( 'plan_title' );
7933
				$site->plan->name  = fs_request_get( 'plan_name' );
7934
7935
				$plans      = array();
7936
				$plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) );
7937
				foreach ( $plans_data as $p ) {
7938
					$plans[] = new FS_Plugin_Plan( $p );
7939
				}
7940
7941
				$this->_set_account( $user, $site, $plans );
7942
7943
				// Reload the page with the keys.
7944
				fs_redirect( $this->_get_admin_page_url() );
7945
			}
7946
		}
7947
7948
		/**
7949
		 * @author Vova Feldman (@svovaf)
7950
		 * @since  1.0.7
7951
		 *
7952
		 * @param string $email
7953
		 *
7954
		 * @return FS_User|bool
7955
		 */
7956
		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...
7957
			self::$_static_logger->entrance();
7958
7959
			$email = trim( strtolower( $email ) );
7960
			$users = self::get_all_users();
7961
			if ( is_array( $users ) ) {
7962
				foreach ( $users as $u ) {
7963
					if ( $email === trim( strtolower( $u->email ) ) ) {
7964
						return $u;
7965
					}
7966
				}
7967
			}
7968
7969
			return false;
7970
		}
7971
7972
		#----------------------------------------------------------------------------------
7973
		#region Account (Loading, Updates & Activation)
7974
		#----------------------------------------------------------------------------------
7975
7976
		/***
7977
		 * Load account information (user + site).
7978
		 *
7979
		 * @author Vova Feldman (@svovaf)
7980
		 * @since  1.0.1
7981
		 */
7982
		private function _load_account() {
7983
			$this->_logger->entrance();
7984
7985
			$this->do_action( 'before_account_load' );
7986
7987
			$sites    = self::get_all_sites( $this->_module_type );
7988
			$users    = self::get_all_users();
7989
			$plans    = self::get_all_plans( $this->_module_type );
7990
			$licenses = self::get_all_licenses( $this->_module_type );
7991
7992
			if ( $this->_logger->is_on() && is_admin() ) {
7993
				$this->_logger->log( 'sites = ' . var_export( $sites, true ) );
7994
				$this->_logger->log( 'users = ' . var_export( $users, true ) );
7995
				$this->_logger->log( 'plans = ' . var_export( $plans, true ) );
7996
				$this->_logger->log( 'licenses = ' . var_export( $licenses, true ) );
7997
			}
7998
7999
			$site = isset( $sites[ $this->_slug ] ) ? $sites[ $this->_slug ] : false;
8000
8001
			if ( is_object( $site ) &&
8002
			     is_numeric( $site->id ) &&
8003
			     is_numeric( $site->user_id ) &&
8004
			     is_object( $site->plan )
8005
			) {
8006
				// Load site.
8007
				$this->_site       = clone $site;
8008
				$this->_site->plan = self::decrypt_entity( $this->_site->plan );
8009
8010
				// Load relevant user.
8011
				$this->_user = clone $users[ $this->_site->user_id ];
8012
8013
				// Load plans.
8014
				$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...
8015
				if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) {
8016
					$this->_sync_plans();
8017
				} else {
8018
					for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
8019
						if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) {
8020
							$this->_plans[ $i ] = self::decrypt_entity( $this->_plans[ $i ] );
8021
						} else {
8022
							unset( $this->_plans[ $i ] );
8023
						}
8024
					}
8025
				}
8026
8027
				// Load licenses.
8028
				$this->_licenses = array();
8029
				if ( is_array( $licenses ) &&
8030
				     isset( $licenses[ $this->_slug ] ) &&
8031
				     isset( $licenses[ $this->_slug ][ $this->_user->id ] )
8032
				) {
8033
					$this->_licenses = $licenses[ $this->_slug ][ $this->_user->id ];
8034
				}
8035
8036
				$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...
8037
8038
				if ( $this->_site->version != $this->get_plugin_version() ) {
8039
					// If stored install version is different than current installed plugin version,
8040
					// then update plugin version event.
8041
					$this->update_plugin_version_event();
8042
				}
8043
			}
8044
8045
			$this->_register_account_hooks();
8046
		}
8047
8048
		/**
8049
		 * @author Vova Feldman (@svovaf)
8050
		 * @since  1.0.1
8051
		 *
8052
		 * @param FS_User    $user
8053
		 * @param FS_Site    $site
8054
		 * @param bool|array $plans
8055
		 */
8056
		private function _set_account( FS_User $user, FS_Site $site, $plans = false ) {
8057
			$site->slug    = $this->_slug;
8058
			$site->user_id = $user->id;
8059
8060
			$this->_site = $site;
8061
			$this->_user = $user;
8062
			if ( false !== $plans ) {
8063
				$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...
8064
			}
8065
8066
			$this->send_install_update();
8067
8068
			$this->_store_account();
8069
8070
		}
8071
8072
		/**
8073
		 * @author Vova Feldman (@svovaf)
8074
		 * @since  1.1.7.4
8075
		 *
8076
		 * @param array $override_with
8077
		 *
8078
		 * @return array
8079
		 */
8080
		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...
8081
			$this->_logger->entrance();
8082
8083
			$current_user = self::_get_current_wp_user();
8084
8085
			$activation_action = $this->get_unique_affix() . '_activate_new';
8086
			$return_url        = $this->is_anonymous() ?
8087
				// If skipped already, then return to the account page.
8088
				$this->get_account_url( $activation_action, array(), false ) :
8089
				// Return to the module's main page.
8090
				$this->get_after_activation_url( 'after_connect_url', array( 'fs_action' => $activation_action ) );
8091
8092
			$params = array(
8093
				'user_firstname'               => $current_user->user_firstname,
8094
				'user_lastname'                => $current_user->user_lastname,
8095
				'user_nickname'                => $current_user->user_nicename,
8096
				'user_email'                   => $current_user->user_email,
8097
				'user_ip'                      => WP_FS__REMOTE_ADDR,
8098
				'plugin_slug'                  => $this->_slug,
8099
				'plugin_id'                    => $this->get_id(),
8100
				'plugin_public_key'            => $this->get_public_key(),
8101
				'plugin_version'               => $this->get_plugin_version(),
8102
				'return_url'                   => fs_nonce_url( $return_url, $activation_action ),
8103
				'account_url'                  => fs_nonce_url( $this->_get_admin_page_url(
8104
					'account',
8105
					array( 'fs_action' => 'sync_user' )
8106
				), 'sync_user' ),
8107
				'site_uid'                     => $this->get_anonymous_id(),
8108
				'site_url'                     => get_site_url(),
8109
				'site_name'                    => get_bloginfo( 'name' ),
8110
				'platform_version'             => get_bloginfo( 'version' ),
8111
				'sdk_version'                  => $this->version,
8112
				'programming_language_version' => phpversion(),
8113
				'language'                     => get_bloginfo( 'language' ),
8114
				'charset'                      => get_bloginfo( 'charset' ),
8115
				'is_premium'                   => $this->is_premium(),
8116
				'is_active'                    => true,
8117
				'is_uninstalled'               => false,
8118
			);
8119
8120
			if ( $this->is_pending_activation() &&
8121
			     ! 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...
8122
			) {
8123
				$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...
8124
			}
8125
8126
			if ( WP_FS__SKIP_EMAIL_ACTIVATION && $this->has_secret_key() ) {
8127
				// Even though rand() is known for its security issues,
8128
				// the timestamp adds another layer of protection.
8129
				// It would be very hard for an attacker to get the secret key form here.
8130
				// Plus, this should never run in production since the secret should never
8131
				// be included in the production version.
8132
				$params['ts']     = WP_FS__SCRIPT_START_TIME;
8133
				$params['salt']   = md5( uniqid( rand() ) );
8134
				$params['secure'] = md5(
8135
					$params['ts'] .
8136
					$params['salt'] .
8137
					$this->get_secret_key()
8138
				);
8139
			}
8140
8141
			return array_merge( $params, $override_with );
8142
		}
8143
8144
		/**
8145
		 * 1. If successful opt-in or pending activation returns the next page that the user should be redirected to.
8146
		 * 2. If there was an API error, return the API result.
8147
		 *
8148
		 * @author Vova Feldman (@svovaf)
8149
		 * @since  1.1.7.4
8150
		 *
8151
		 * @param string|bool $email
8152
		 * @param string|bool $first
8153
		 * @param string|bool $last
8154
		 * @param string|bool $license_key
8155
		 * @param bool        $is_uninstall       If "true", this means that the module is currently being uninstalled.
8156
		 *                                        In this case, the user and site info will be sent to the server but no
8157
		 *                                        data will be saved to the WP installation's database.
8158
		 * @param number|bool $trial_plan_id
8159
		 *
8160
		 * @return string|object
8161
		 * @use    WP_Error
8162
		 */
8163
		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...
8164
			$email = false,
8165
			$first = false,
8166
			$last = false,
8167
			$license_key = false,
8168
			$is_uninstall = false,
8169
			$trial_plan_id = false
8170
		) {
8171
			$this->_logger->entrance();
8172
8173
			if ( false === $email ) {
8174
				$current_user = self::_get_current_wp_user();
8175
				$email        = $current_user->user_email;
8176
			}
8177
8178
			/**
8179
			 * @since 1.2.1 If activating with license key, ignore the context-user
8180
			 *              since the user will be automatically loaded from the license.
8181
			 */
8182
			if ( empty( $license_key ) ) {
8183
				// Clean up pending license if opt-ing in again.
8184
				$this->_storage->remove( 'pending_license_key' );
8185
8186
				if ( ! $is_uninstall ) {
8187
					$fs_user = Freemius::_get_user_by_email( $email );
8188
					if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) {
8189
						return $this->install_with_current_user( false, $trial_plan_id );
8190
					}
8191
				}
8192
			}
8193
8194
			$user_info = array();
8195
			if ( ! empty( $email ) ) {
8196
				$user_info['user_email'] = $email;
8197
			}
8198
			if ( ! empty( $first ) ) {
8199
				$user_info['user_firstname'] = $first;
8200
			}
8201
			if ( ! empty( $last ) ) {
8202
				$user_info['user_lastname'] = $last;
8203
			}
8204
8205
			$params = $this->get_opt_in_params( $user_info );
8206
8207
			$filtered_license_key = false;
8208
			if ( is_string( $license_key ) ) {
8209
				$filtered_license_key  = $this->apply_filters( 'license_key', $license_key );
8210
				$params['license_key'] = $filtered_license_key;
8211
			} else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) {
8212
				$params['trial_plan_id'] = $trial_plan_id;
8213
			}
8214
8215
			if ( $is_uninstall ) {
8216
				$params['uninstall_params'] = array(
8217
					'reason_id'   => $this->_storage->uninstall_reason->id,
8218
					'reason_info' => $this->_storage->uninstall_reason->info
8219
				);
8220
			}
8221
8222
			$params['format'] = 'json';
8223
8224
			$url = WP_FS__ADDRESS . '/action/service/user/install/';
8225
			if ( isset( $_COOKIE['XDEBUG_SESSION'] ) ) {
8226
				$url = add_query_arg( 'XDEBUG_SESSION', 'PHPSTORM', $url );
8227
			}
8228
8229
			$response = wp_remote_post( $url, array(
8230
				'method'  => 'POST',
8231
				'body'    => $params,
8232
				'timeout' => 15,
8233
			) );
8234
8235
			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...
8236
				if ( 'https://' === substr( $url, 0, 8 ) &&
8237
				     isset( $response->errors ) &&
8238
				     isset( $response->errors['http_request_failed'] )
8239
				) {
8240
					$http_error = strtolower( $response->errors['http_request_failed'][0] );
8241
8242
					if ( false !== strpos( $http_error, 'ssl' ) ) {
8243
						// Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare).
8244
						$url = 'http://' . substr( $url, 8 );
8245
8246
						$response = wp_remote_post( $url, array(
8247
							'method'  => 'POST',
8248
							'body'    => $params,
8249
							'timeout' => 15,
8250
						) );
8251
					}
8252
				}
8253
			}
8254
8255
			if ( is_wp_error( $response ) ) {
8256
				/**
8257
				 * @var WP_Error $response
8258
				 */
8259
				$result = new stdClass();
8260
8261
				$error_code = $response->get_error_code();
8262
				$error_type = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $error_code ) ) );
8263
8264
				$result->error = (object) array(
8265
					'type'    => $error_type,
8266
					'message' => $response->get_error_message(),
8267
					'code'    => $error_code,
8268
					'http'    => 402
8269
				);
8270
8271
				return $result;
8272
			}
8273
8274
			// Module is being uninstalled, don't handle the returned data.
8275
			if ( $is_uninstall ) {
8276
				return true;
8277
			}
8278
8279
			$decoded = @json_decode( $response['body'] );
8280
8281
			if ( empty( $decoded ) ) {
8282
				return false;
8283
			}
8284
8285
			if ( ! $this->is_api_result_object( $decoded ) ) {
8286
				if ( ! empty( $params['license_key'] ) ) {
8287
					// Pass the fully entered license key to the failure handler.
8288
					$params['license_key'] = $license_key;
8289
				}
8290
8291
				return $is_uninstall ?
8292
					$decoded :
8293
					$this->apply_filters( 'after_install_failure', $decoded, $params );
8294
			} else if ( isset( $decoded->pending_activation ) && $decoded->pending_activation ) {
8295
				// Pending activation, add message.
8296
				return $this->set_pending_confirmation(
8297
					true,
8298
					false,
8299
					$filtered_license_key,
8300
					! empty( $params['trial_plan_id'] )
8301
				);
8302
			} else if ( isset( $decoded->install_secret_key ) ) {
8303
				return $this->install_with_new_user(
8304
					$decoded->user_id,
8305
					$decoded->user_public_key,
8306
					$decoded->user_secret_key,
8307
					$decoded->install_id,
8308
					$decoded->install_public_key,
8309
					$decoded->install_secret_key,
8310
					false
8311
				);
8312
			}
8313
8314
			return $decoded;
8315
		}
8316
8317
		/**
8318
		 * Set user and site identities.
8319
		 *
8320
		 * @author Vova Feldman (@svovaf)
8321
		 * @since  1.0.9
8322
		 *
8323
		 * @param FS_User $user
8324
		 * @param FS_Site $site
8325
		 * @param bool    $redirect
8326
		 * @param bool    $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will
8327
		 *                              redirect (or return a URL) to the account page with a special parameter to
8328
		 *                              trigger the auto installation processes.
8329
		 *
8330
		 * @return string If redirect is `false`, returns the next page the user should be redirected to.
8331
		 */
8332
		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...
8333
			FS_User $user,
8334
			FS_Site $site,
8335
			$redirect = true,
8336
			$auto_install = false
8337
		) {
8338
			$this->_user = $user;
8339
			$this->_site = $site;
8340
8341
			$this->_sync_plans();
8342
8343
			$this->_enrich_site_plan( false );
8344
8345
			$this->_set_account( $user, $site );
8346
8347
			if ( $this->is_trial() ) {
8348
				// Store trial plan information.
8349
				$this->_enrich_site_trial_plan( true );
8350
			}
8351
8352
			// If Freemius was OFF before, turn it on.
8353
			$this->turn_on();
8354
8355
			$this->do_action( 'after_account_connection', $user, $site );
8356
8357
			if ( is_numeric( $site->license_id ) ) {
8358
				$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...
8359
			}
8360
8361
			$this->_admin_notices->remove_sticky( 'connect_account' );
8362
8363
			if ( $this->is_pending_activation() ) {
8364
				// Remove pending activation sticky notice (if still exist).
8365
				$this->_admin_notices->remove_sticky( 'activation_pending' );
8366
8367
				// Remove plugin from pending activation mode.
8368
				unset( $this->_storage->is_pending_activation );
8369
8370
				if ( ! $this->is_paying_or_trial() ) {
8371
					$this->_admin_notices->add_sticky(
8372
						sprintf( $this->get_text( 'plugin-x-activation-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
8373
						'activation_complete'
8374
					);
8375
				}
8376
			}
8377
8378
			if ( $this->is_paying_or_trial() ) {
8379
				if ( ! $this->is_premium() || ! $this->has_premium_version() ) {
8380
					if ( $this->is_paying() ) {
8381
						$this->_admin_notices->add_sticky(
8382
							sprintf(
8383
								$this->get_text( 'activation-with-plan-x-message' ),
8384
								$this->_site->plan->title
8385
							) . $this->get_complete_upgrade_instructions(),
8386
							'plan_upgraded',
8387
							$this->get_text( 'yee-haw' ) . '!'
8388
						);
8389
					} else {
8390
						$this->_admin_notices->add_sticky(
8391
							sprintf(
8392
								$this->get_text( 'trial-started-message' ),
8393
								'<i>' . $this->get_plugin_name() . '</i>'
8394
							) . $this->get_complete_upgrade_instructions( $this->_storage->trial_plan->title ),
8395
							'trial_started',
8396
							$this->get_text( 'yee-haw' ) . '!'
8397
						);
8398
					}
8399
				}
8400
8401
				$this->_admin_notices->remove_sticky( array(
8402
					'trial_promotion',
8403
				) );
8404
			}
8405
8406
			$plugin_id = fs_request_get( 'plugin_id', false );
8407
8408
			// Store activation time ONLY for plugins (not add-ons).
8409
			if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) {
8410
				$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
8411
			}
8412
8413
			$next_page = '';
8414
8415
			$extra = array();
8416
			if ( $auto_install ) {
8417
				$extra['auto_install'] = 'true';
8418
			}
8419
8420
			if ( is_numeric( $plugin_id ) ) {
8421
				/**
8422
				 * @author Leo Fajardo
8423
				 * @since  1.2.1.6
8424
				 *
8425
				 * Also sync the license after an anonymous user subscribes.
8426
				 */
8427
				if ( $this->is_anonymous() || $plugin_id != $this->_plugin->id ) {
8428
					// Add-on was installed - sync license right after install.
8429
					$next_page = $this->_get_sync_license_url( $plugin_id, true, $extra );
8430
				}
8431
			} else {
8432
				/**
8433
				 * @author Vova Feldman (@svovaf)
8434
				 * @since  1.1.9 If site installed with a valid license, sync license.
8435
				 */
8436
				if ( $this->is_paying() ) {
8437
					$this->_sync_plugin_license( true );
8438
				}
8439
8440
				// Reload the page with the keys.
8441
				$next_page = $this->is_anonymous() ?
8442
					// If user previously skipped, redirect to account page.
8443
					$this->get_account_url( false, $extra ) :
8444
					$this->get_after_activation_url( 'after_connect_url' );
8445
			}
8446
8447
			if ( ! empty( $next_page ) && $redirect ) {
8448
				fs_redirect( $next_page );
8449
			}
8450
8451
			return $next_page;
8452
		}
8453
8454
		/**
8455
		 * Install plugin with new user information after approval.
8456
		 *
8457
		 * @author Vova Feldman (@svovaf)
8458
		 * @since  1.0.7
8459
		 */
8460
		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...
8461
			$this->_logger->entrance();
8462
8463
			if ( $this->is_registered() ) {
8464
				return;
8465
			}
8466
8467
			if ( ( $this->is_plugin() && fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) ||
8468
				// @todo This logic should be improved because it's executed on every load of a theme.
8469
			     $this->is_theme()
8470
			) {
8471
//				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...
8472
8473
				if ( fs_request_has( 'user_secret_key' ) ) {
8474
					$this->install_with_new_user(
8475
						fs_request_get( 'user_id' ),
8476
						fs_request_get( 'user_public_key' ),
8477
						fs_request_get( 'user_secret_key' ),
8478
						fs_request_get( 'install_id' ),
8479
						fs_request_get( 'install_public_key' ),
8480
						fs_request_get( 'install_secret_key' ),
8481
						true,
8482
						fs_request_get_bool( 'auto_install' )
8483
					);
8484
				} else if ( fs_request_has( 'pending_activation' ) ) {
8485
					$this->set_pending_confirmation( fs_request_get( 'user_email' ), true );
8486
				}
8487
			}
8488
		}
8489
8490
		/**
8491
		 * Install plugin with new user.
8492
		 *
8493
		 * @author Vova Feldman (@svovaf)
8494
		 * @since  1.1.7.4
8495
		 *
8496
		 * @param number $user_id
8497
		 * @param string $user_public_key
8498
		 * @param string $user_secret_key
8499
		 * @param number $install_id
8500
		 * @param string $install_public_key
8501
		 * @param string $install_secret_key
8502
		 * @param bool   $redirect
8503
		 * @param bool   $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will
8504
		 *                             redirect (or return a URL) to the account page with a special parameter to
8505
		 *                             trigger the auto installation processes.
8506
		 *
8507
		 * @return string If redirect is `false`, returns the next page the user should be redirected to.
8508
		 */
8509
		private function install_with_new_user(
8510
			$user_id,
8511
			$user_public_key,
8512
			$user_secret_key,
8513
			$install_id,
8514
			$install_public_key,
8515
			$install_secret_key,
8516
			$redirect = true,
8517
			$auto_install = false
8518
		) {
8519
			$user             = new FS_User();
8520
			$user->id         = $user_id;
8521
			$user->public_key = $user_public_key;
8522
			$user->secret_key = $user_secret_key;
8523
8524
			$this->_user = $user;
8525
			$user_result = $this->get_api_user_scope()->get();
8526
			$user        = new FS_User( $user_result );
8527
			$this->_user = $user;
8528
8529
			$site             = new FS_Site();
8530
			$site->id         = $install_id;
8531
			$site->public_key = $install_public_key;
8532
			$site->secret_key = $install_secret_key;
8533
8534
			$this->_site = $site;
8535
			$site_result = $this->get_api_site_scope()->get();
8536
			$site        = new FS_Site( $site_result );
8537
			$this->_site = $site;
8538
8539
			return $this->setup_account(
8540
				$this->_user,
8541
				$this->_site,
8542
				$redirect,
8543
				$auto_install
8544
			);
8545
		}
8546
8547
		/**
8548
		 * @author Vova Feldman (@svovaf)
8549
		 * @since  1.1.7.4
8550
		 *
8551
		 * @param string|bool $email
8552
		 * @param bool        $redirect
8553
		 * @param string|bool $license_key      Since 1.2.1.5
8554
		 * @param bool        $is_pending_trial Since 1.2.1.5
8555
		 *
8556
		 * @return string Since 1.2.1.5 if $redirect is `false`, return the pending activation page.
8557
		 */
8558
		private function set_pending_confirmation(
8559
			$email = false,
8560
			$redirect = true,
8561
			$license_key = false,
8562
			$is_pending_trial = false
8563
		) {
8564
			if ( $this->_ignore_pending_mode ) {
8565
				/**
8566
				 * If explicitly asked to ignore pending mode, set to anonymous mode
8567
				 * if require confirmation before finalizing the opt-in.
8568
				 *
8569
				 * @author Vova Feldman
8570
				 * @since  1.2.1.6
8571
				 */
8572
				$this->skip_connection();
8573
			} else {
8574
				// Install must be activated via email since
8575
				// user with the same email already exist.
8576
				$this->_storage->is_pending_activation = true;
8577
				$this->_add_pending_activation_notice( $email, $is_pending_trial );
8578
			}
8579
8580
			if ( ! empty( $license_key ) ) {
8581
				$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...
8582
			}
8583
8584
			// Remove the opt-in sticky notice.
8585
			$this->_admin_notices->remove_sticky( array(
8586
				'connect_account',
8587
				'trial_promotion',
8588
			) );
8589
8590
			$next_page = $this->get_after_activation_url( 'after_pending_connect_url' );
8591
8592
			// Reload the page with with pending activation message.
8593
			if ( $redirect ) {
8594
				fs_redirect( $next_page );
8595
			}
8596
8597
			return $next_page;
8598
		}
8599
8600
		/**
8601
		 * Install plugin with current logged WP user info.
8602
		 *
8603
		 * @author Vova Feldman (@svovaf)
8604
		 * @since  1.0.7
8605
		 */
8606
		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...
8607
			$this->_logger->entrance();
8608
8609
			if ( $this->is_registered() ) {
8610
				return;
8611
			}
8612
8613
			if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) && fs_request_is_post() ) {
8614
//				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...
8615
8616
				/**
8617
				 * @author Vova Feldman (@svovaf)
8618
				 * @since  1.1.9 Add license key if given.
8619
				 */
8620
				$license_key = fs_request_get( 'license_secret_key' );
8621
8622
				$this->install_with_current_user( $license_key );
8623
			}
8624
		}
8625
8626
8627
		/**
8628
		 * @author Vova Feldman (@svovaf)
8629
		 * @since  1.1.7.4
8630
		 *
8631
		 * @param string|bool $license_key
8632
		 * @param number|bool $trial_plan_id
8633
		 * @param bool        $redirect
8634
		 *
8635
		 * @return string|object If redirect is `false`, returns the next page the user should be redirected to, or the
8636
		 *                       API error object if failed to install.
8637
		 */
8638
		private function install_with_current_user(
8639
			$license_key = false,
8640
			$trial_plan_id = false,
8641
			$redirect = true
8642
		) {
8643
			// Get current logged WP user.
8644
			$current_user = self::_get_current_wp_user();
8645
8646
			// Find the relevant FS user by the email.
8647
			$user = self::_get_user_by_email( $current_user->user_email );
8648
8649
			// We have to set the user before getting user scope API handler.
8650
			$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...
8651
8652
			$extra_install_params = array(
8653
				'uid' => $this->get_anonymous_id(),
8654
			);
8655
8656
			if ( ! empty( $license_key ) ) {
8657
				$filtered_license_key                = $this->apply_filters( 'license_key', $license_key );
8658
				$extra_install_params['license_key'] = $filtered_license_key;
8659
			} else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) {
8660
				$extra_install_params['trial_plan_id'] = $trial_plan_id;
8661
			}
8662
8663
			$args = $this->get_install_data_for_api( $extra_install_params, false, false );
8664
8665
			// Install the plugin.
8666
			$install = $this->get_api_user_scope()->call(
8667
				"/plugins/{$this->get_id()}/installs.json",
8668
				'post',
8669
				$args
8670
			);
8671
8672
			if ( ! $this->is_api_result_entity( $install ) ) {
8673
				if ( ! empty( $args['license_key'] ) ) {
8674
					// Pass full the fully entered license key to the failure handler.
8675
					$args['license_key'] = $license_key;
8676
				}
8677
8678
				$install = $this->apply_filters( 'after_install_failure', $install, $args );
8679
8680
				$this->_admin_notices->add(
8681
					sprintf( $this->get_text( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
8682
					$this->get_text( 'contact-us-with-error-message' ) . ' ' . '<b>' . $install->error->message . '</b>',
8683
					$this->get_text( 'oops' ) . '...',
8684
					'error'
8685
				);
8686
8687
				if ( $redirect ) {
8688
					fs_redirect( $this->get_activation_url( array( 'error' => $install->error->message ) ) );
8689
				}
8690
8691
				return $install;
8692
			}
8693
8694
			$site        = new FS_Site( $install );
8695
			$this->_site = $site;
8696
8697
			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...
8698
		}
8699
8700
		/**
8701
		 * Tries to activate add-on account based on parent plugin info.
8702
		 *
8703
		 * @author Vova Feldman (@svovaf)
8704
		 * @since  1.0.6
8705
		 *
8706
		 * @param Freemius $parent_fs
8707
		 */
8708
		private function _activate_addon_account( Freemius $parent_fs ) {
8709
			if ( $this->is_registered() ) {
8710
				// Already activated.
8711
				return;
8712
			}
8713
8714
			// Activate add-on with parent plugin credentials.
8715
			$addon_install = $parent_fs->get_api_site_scope()->call(
8716
				"/addons/{$this->_plugin->id}/installs.json",
8717
				'post',
8718
				$this->get_install_data_for_api( array(
8719
					'uid' => $this->get_anonymous_id(),
8720
				), false, false )
8721
			);
8722
8723
			if ( isset( $addon_install->error ) ) {
8724
				$this->_admin_notices->add(
8725
					sprintf( $this->get_text( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
8726
					$this->get_text( 'contact-us-with-error-message' ) . ' ' . '<b>' . $addon_install->error->message . '</b>',
8727
					$this->get_text( 'oops' ) . '...',
8728
					'error'
8729
				);
8730
8731
				return;
8732
			}
8733
8734
			// First of all, set site info - otherwise we won't
8735
			// be able to invoke API calls.
8736
			$this->_site = new FS_Site( $addon_install );
8737
8738
			// Sync add-on plans.
8739
			$this->_sync_plans();
8740
8741
			// Get site's current plan.
8742
			$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...
8743
8744
			// Get user information based on parent's plugin.
8745
			$user = $parent_fs->get_user();
8746
8747
			$this->_set_account( $user, $this->_site );
8748
8749
			// Sync licenses.
8750
			$this->_sync_licenses();
8751
8752
			// Try to activate premium license.
8753
			$this->_activate_license( true );
8754
		}
8755
8756
		/**
8757
		 * Tries to activate parent account based on add-on's info.
8758
		 *
8759
		 * @author Vova Feldman (@svovaf)
8760
		 * @since  1.2.2.7
8761
		 *
8762
		 * @param Freemius $parent_fs
8763
		 */
8764
		private function activate_parent_account( Freemius $parent_fs ) {
8765
			if ( ! $this->is_addon() ) {
8766
				// This is not an add-on.
8767
				return;
8768
			}
8769
8770
			if ( $parent_fs->is_registered() ) {
8771
				// Already activated.
8772
				return;
8773
			}
8774
8775
			// Activate parent with add-on's user credentials.
8776
			$parent_install = $this->get_api_user_scope()->call(
8777
				"/plugins/{$parent_fs->_plugin->id}/installs.json",
8778
				'post',
8779
				$parent_fs->get_install_data_for_api( array(
8780
					'uid' => $parent_fs->get_anonymous_id(),
8781
				), false, false )
8782
			);
8783
8784
			if ( isset( $parent_install->error ) ) {
8785
				$this->_admin_notices->add(
8786
					sprintf( $this->get_text( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
8787
					$this->get_text( 'contact-us-with-error-message' ) . ' ' . '<b>' . $parent_install->error->message . '</b>',
8788
					$this->get_text( 'oops' ) . '...',
8789
					'error'
8790
				);
8791
8792
				return;
8793
			}
8794
8795
			// First of all, set site info - otherwise we won't
8796
			// be able to invoke API calls.
8797
			$parent_fs->_site = new FS_Site( $parent_install );
8798
8799
			// Sync add-on plans.
8800
			$parent_fs->_sync_plans();
8801
8802
			// Get site's current plan.
8803
			$parent_fs->_site->plan = $parent_fs->_get_plan_by_id( $parent_fs->_site->plan->id );
0 ignored issues
show
Documentation Bug introduced by
It seems like $parent_fs->_get_plan_by...nt_fs->_site->plan->id) can also be of type false. However, the property $plan is declared as type object<FS_Plugin_Plan>. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
8804
8805
			// Get user information based on parent's plugin.
8806
			$user = $this->get_user();
8807
8808
			$parent_fs->_set_account( $user, $parent_fs->_site );
8809
		}
8810
8811
		#endregion
8812
8813
		#----------------------------------------------------------------------------------
8814
		#region Admin Menu Items
8815
		#----------------------------------------------------------------------------------
8816
8817
		private $_menu_items = array();
8818
8819
		/**
8820
		 * @author Vova Feldman (@svovaf)
8821
		 * @since  1.2.1.8
8822
		 *
8823
		 * @return array
8824
		 */
8825
		function get_menu_items() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8826
			return $this->_menu_items;
8827
		}
8828
8829
		/**
8830
		 * @author Vova Feldman (@svovaf)
8831
		 * @since  1.0.7
8832
		 *
8833
		 * @return string
8834
		 */
8835
		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...
8836
			return $this->_menu->get_slug();
8837
		}
8838
8839
		/**
8840
		 * @author Vova Feldman (@svovaf)
8841
		 * @since  1.0.9
8842
		 */
8843
		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...
8844
//			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...
8845
//				return;
8846
//			}
8847
8848
			if ( ! $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) {
8849
				$this->_menu->remove_menu_item();
8850
			} else {
8851
				$this->do_action( 'before_admin_menu_init' );
8852
8853
				$this->add_menu_action();
8854
				$this->add_submenu_items();
8855
			}
8856
		}
8857
8858
		/**
8859
		 * Admin dashboard menu items modifications.
8860
		 *
8861
		 * NOTE: admin_menu action executed before admin_init.
8862
		 *
8863
		 * @author Vova Feldman (@svovaf)
8864
		 * @since  1.0.7
8865
		 *
8866
		 */
8867
		private function add_menu_action() {
8868
			if ( $this->is_activation_mode() ) {
8869
				if ( $this->is_plugin() || ( $this->has_settings_menu() && ! $this->is_free_wp_org_theme() ) ) {
8870
					$this->override_plugin_menu_with_activation();
8871
				} else {
8872
					/**
8873
					 * Handle theme opt-in when the opt-in form shows as a dialog box in the themes page.
8874
					 */
8875
					if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) {
8876
						add_action( 'load-themes.php', array( &$this, '_install_with_current_user' ) );
8877
					} else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ||
8878
					            fs_request_get_bool( 'pending_activation' )
8879
					) {
8880
						add_action( 'load-themes.php', array( &$this, '_install_with_new_user' ) );
8881
					}
8882
				}
8883
			} else {
8884
				if ( ! $this->is_registered() ) {
8885
					// If not registered try to install user.
8886
					if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) {
8887
						$this->_install_with_new_user();
8888
					}
8889
				} else if (
8890
					fs_request_is_action( 'sync_user' ) &&
8891
					( ! $this->has_settings_menu() || $this->is_free_wp_org_theme() )
8892
				) {
8893
					$this->_handle_account_user_sync();
8894
				}
8895
			}
8896
		}
8897
8898
		/**
8899
		 * @author Vova Feldman (@svovaf)
8900
		 * @since  1.0.1
8901
		 */
8902
		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...
8903
			$this->_logger->entrance();
8904
8905
			$page = strtolower( isset( $_REQUEST['page'] ) ? $_REQUEST['page'] : '' );
8906
8907
			$this->_logger->log( 'page = ' . $page );
8908
8909
			foreach ( $this->_menu_items as $priority => $items ) {
8910
				foreach ( $items as $item ) {
8911
					if ( isset( $item['url'] ) ) {
8912
						if ( $page === $this->_menu->get_slug( strtolower( $item['menu_slug'] ) ) ) {
8913
							$this->_logger->log( 'Redirecting to ' . $item['url'] );
8914
8915
							fs_redirect( $item['url'] );
8916
						}
8917
					}
8918
				}
8919
			}
8920
		}
8921
8922
		/**
8923
		 * Remove plugin's all admin menu items & pages, and replace with activation page.
8924
		 *
8925
		 * @author Vova Feldman (@svovaf)
8926
		 * @since  1.0.1
8927
		 */
8928
		private function override_plugin_menu_with_activation() {
8929
			$this->_logger->entrance();
8930
8931
			$hook = false;
8932
8933
			if ( ! $this->_menu->has_menu() ) {
8934
				// Add the opt-in page without a menu item.
8935
				$hook = FS_Admin_Menu_Manager::add_subpage(
8936
					null,
8937
					$this->get_plugin_name(),
8938
					$this->get_plugin_name(),
8939
					'manage_options',
8940
					$this->_slug,
8941
					array( &$this, '_connect_page_render' )
8942
				);
8943
			} else if ( $this->_menu->is_top_level() ) {
8944
				$hook = $this->_menu->override_menu_item( array( &$this, '_connect_page_render' ) );
8945
8946
				if ( false === $hook ) {
8947
					// Create new menu item just for the opt-in.
8948
					$hook = FS_Admin_Menu_Manager::add_page(
8949
						$this->get_plugin_name(),
8950
						$this->get_plugin_name(),
8951
						'manage_options',
8952
						$this->_menu->get_slug(),
8953
						array( &$this, '_connect_page_render' )
8954
					);
8955
				}
8956
			} else {
8957
				$menus = array( $this->_menu->get_parent_slug() );
8958
8959
				if ( $this->_menu->is_override_exact() ) {
8960
					// Make sure the current page is matching the activation page.
8961
					if ( ! $this->is_matching_url( $this->get_activation_url() ) ) {
8962
						return;
8963
					}
8964
				}
8965
8966
				foreach ( $menus as $parent_slug ) {
8967
					$hook = $this->_menu->override_submenu_action(
8968
						$parent_slug,
8969
						$this->_menu->get_raw_slug(),
8970
						array( &$this, '_connect_page_render' )
8971
					);
8972
8973
					if ( false !== $hook ) {
8974
						// Found plugin's submenu item.
8975
						break;
8976
					}
8977
				}
8978
			}
8979
8980
			if ( $this->is_activation_page() ) {
8981
				// Clean admin page from distracting content.
8982
				self::_clean_admin_content_section();
8983
			}
8984
8985
			if ( false !== $hook ) {
8986
				if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) {
8987
					add_action( "load-$hook", array( &$this, '_install_with_current_user' ) );
8988
				} else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) {
8989
					add_action( "load-$hook", array( &$this, '_install_with_new_user' ) );
8990
				}
8991
			}
8992
		}
8993
8994
		/**
8995
		 * @author Leo Fajardo (leorw)
8996
		 * @since  1.2.1
8997
		 *
8998
		 * return string
8999
		 */
9000
		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...
9001
			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...
9002
9003
			$top_level_menu_slug = $this->get_top_level_menu_slug();
9004
9005
			foreach ( $menu as $menu_info ) {
9006
				/**
9007
				 * The second element in the menu info array is the capability/role that has access to the menu and the
9008
				 * third element is the menu slug.
9009
				 */
9010
				if ( $menu_info[2] === $top_level_menu_slug ) {
9011
					return $menu_info[1];
9012
				}
9013
			}
9014
9015
			return 'read';
9016
		}
9017
9018
		/**
9019
		 * @author Vova Feldman (@svovaf)
9020
		 * @since  1.0.0
9021
		 *
9022
		 * @return string
9023
		 */
9024
		private function get_top_level_menu_slug() {
9025
			return ( $this->is_addon() ?
9026
				$this->get_parent_instance()->_menu->get_top_level_menu_slug() :
9027
				$this->_menu->get_top_level_menu_slug() );
9028
		}
9029
9030
		/**
9031
		 * @author Vova Feldman (@svovaf)
9032
		 * @since  1.2.2.7
9033
		 *
9034
		 * @return string
9035
		 */
9036
		function get_pricing_cta_label() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9037
			$label = 'upgrade';
9038
9039
			if ( $this->is_in_trial_promotion() &&
9040
			     ! $this->is_paying_or_trial()
9041
			) {
9042
				// If running a trial promotion, modify the pricing to load the trial.
9043
				$label = 'start-trial';
9044
			} else if ( $this->is_paying() ) {
9045
				$label = 'pricing';
9046
			}
9047
9048
			return $label;
9049
		}
9050
9051
		/**
9052
		 * @author Vova Feldman (@svovaf)
9053
		 * @since  1.2.2.7
9054
		 *
9055
		 * @return bool
9056
		 */
9057
		function is_pricing_page_visible() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9058
			return (
9059
				// Has at least one paid plan.
9060
				$this->has_paid_plan() &&
9061
				// Didn't ask to hide the pricing page.
9062
				$this->is_page_visible( 'pricing' ) &&
9063
				// Don't have a valid active license or has more than one plan.
9064
				( ! $this->is_paying() || ! $this->is_single_plan() )
9065
			);
9066
		}
9067
9068
		/**
9069
		 * Add default Freemius menu items.
9070
		 *
9071
		 * @author Vova Feldman (@svovaf)
9072
		 * @since  1.0.0
9073
		 */
9074
		private function add_submenu_items() {
9075
			$this->_logger->entrance();
9076
9077
			if ( ! $this->is_addon() ) {
9078
				/**
9079
				 * @since 1.2.2.7 Also add submenu items when running in a free .org theme so the tabs will be visible.
9080
				 */
9081
				if ( ! $this->is_activation_mode() || $this->is_free_wp_org_theme() ) {
9082
					if ( $this->is_registered() ) {
9083
						$show_account = (
9084
							$this->is_submenu_item_visible( 'account' ) &&
9085
							/**
9086
							 * @since 1.2.2.7 Don't show the Account for free WP.org themes without any paid plans.
9087
							 */
9088
							( ! $this->is_free_wp_org_theme() || $this->has_paid_plan() )
9089
						);
9090
9091
						// Add user account page.
9092
						$this->add_submenu_item(
9093
							$this->get_text( 'account' ),
9094
							array( &$this, '_account_page_render' ),
9095
							$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'account' ),
9096
							'manage_options',
9097
							'account',
9098
							array( &$this, '_account_page_load' ),
9099
							WP_FS__DEFAULT_PRIORITY,
9100
							$show_account
9101
						);
9102
					}
9103
9104
					// Add contact page.
9105
					$this->add_submenu_item(
9106
						$this->get_text( 'contact-us' ),
9107
						array( &$this, '_contact_page_render' ),
9108
						$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'contact-us' ),
9109
						'manage_options',
9110
						'contact',
9111
						'Freemius::_clean_admin_content_section',
9112
						WP_FS__DEFAULT_PRIORITY,
9113
						$this->is_submenu_item_visible( 'contact' )
9114
					);
9115
9116
					if ( $this->has_addons() ) {
9117
						$this->add_submenu_item(
9118
							$this->get_text( 'add-ons' ),
9119
							array( &$this, '_addons_page_render' ),
9120
							$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'add-ons' ),
9121
							'manage_options',
9122
							'addons',
9123
							array( &$this, '_addons_page_load' ),
9124
							WP_FS__LOWEST_PRIORITY - 1,
9125
							$this->is_submenu_item_visible( 'addons' )
9126
						);
9127
					}
9128
9129
					$show_pricing = (
9130
						$this->is_submenu_item_visible( 'pricing' ) &&
9131
						$this->is_pricing_page_visible()
9132
					);
9133
9134
					$pricing_cta_slug = $this->get_pricing_cta_label();
9135
					$pricing_class    = 'upgrade-mode';
9136
					if ( $show_pricing ) {
9137
						if ( $this->is_in_trial_promotion() &&
9138
						     ! $this->is_paying_or_trial()
9139
						) {
9140
							// If running a trial promotion, modify the pricing to load the trial.
9141
							$pricing_class    = 'trial-mode';
9142
						} else if ( $this->is_paying() ) {
9143
							$pricing_class    = '';
9144
						}
9145
					}
9146
9147
					// Add upgrade/pricing page.
9148
					$this->add_submenu_item(
9149
						$this->get_text( $pricing_cta_slug ) . '&nbsp;&nbsp;' . ( is_rtl() ? '&#x2190;' : '&#x27a4;' ),
9150
						array( &$this, '_pricing_page_render' ),
9151
						$this->get_plugin_name() . ' &ndash; ' . $this->get_text( 'pricing' ),
9152
						'manage_options',
9153
						'pricing',
9154
						'Freemius::_clean_admin_content_section',
9155
						WP_FS__LOWEST_PRIORITY,
9156
						$show_pricing,
9157
						$pricing_class
9158
					);
9159
				}
9160
			}
9161
9162
9163
			if ( 0 < count( $this->_menu_items ) ) {
9164
				if ( ! $this->_menu->is_top_level() ) {
9165
					fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
9166
9167
					// Append submenu items right after the plugin's submenu item.
9168
					$this->order_sub_submenu_items();
9169
				} else {
9170
					// Append submenu items.
9171
					$this->embed_submenu_items();
9172
				}
9173
			}
9174
		}
9175
9176
		/**
9177
		 * Moved the actual submenu item additions to a separated function,
9178
		 * in order to support sub-submenu items when the plugin's settings
9179
		 * only have a submenu and not top-level menu item.
9180
		 *
9181
		 * @author Vova Feldman (@svovaf)
9182
		 * @since  1.1.4
9183
		 */
9184
		private function embed_submenu_items() {
9185
			$item_template = $this->_menu->is_top_level() ?
9186
				'<span class="fs-submenu-item %s %s %s">%s</span>' :
9187
				'<span class="fs-submenu-item fs-sub %s %s %s">%s</span>';
9188
9189
			$top_level_menu_capability = $this->get_top_level_menu_capability();
9190
9191
			ksort( $this->_menu_items );
9192
9193
			foreach ( $this->_menu_items as $priority => $items ) {
9194
				foreach ( $items as $item ) {
9195
					$capability = ( ! empty( $item['capability'] ) ? $item['capability'] : $top_level_menu_capability );
9196
9197
					$menu_item = sprintf(
9198
						$item_template,
9199
						$this->get_unique_affix(),
9200
						$item['menu_slug'],
9201
						! empty( $item['class'] ) ? $item['class'] : '',
9202
						$item['menu_title']
9203
					);
9204
9205
					$menu_slug = $this->_menu->get_slug( $item['menu_slug'] );
9206
9207
					if ( ! isset( $item['url'] ) ) {
9208
						$hook = FS_Admin_Menu_Manager::add_subpage(
9209
							$item['show_submenu'] ?
9210
								$this->get_top_level_menu_slug() :
9211
								null,
9212
							$item['page_title'],
9213
							$menu_item,
9214
							$capability,
9215
							$menu_slug,
9216
							$item['render_function']
9217
						);
9218
9219
						if ( false !== $item['before_render_function'] ) {
9220
							add_action( "load-$hook", $item['before_render_function'] );
9221
						}
9222
					} else {
9223
						FS_Admin_Menu_Manager::add_subpage(
9224
							$item['show_submenu'] ?
9225
								$this->get_top_level_menu_slug() :
9226
								null,
9227
							$item['page_title'],
9228
							$menu_item,
9229
							$capability,
9230
							$menu_slug,
9231
							array( $this, '' )
9232
						);
9233
					}
9234
				}
9235
			}
9236
		}
9237
9238
		/**
9239
		 * Re-order the submenu items so all Freemius added new submenu items
9240
		 * are added right after the plugin's settings submenu item.
9241
		 *
9242
		 * @author Vova Feldman (@svovaf)
9243
		 * @since  1.1.4
9244
		 */
9245
		private function order_sub_submenu_items() {
9246
			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...
9247
9248
			$menu_slug = $this->_menu->get_top_level_menu_slug();
9249
9250
			/**
9251
			 * Before "admin_menu" fires, WordPress will loop over the default submenus and remove pages for which the user
9252
			 * does not have permissions. So in case a plugin does not have top-level menu but does have submenus under any
9253
			 * of the default menus, only users that have the right role can access its sub-submenus (Account, Contact Us,
9254
			 * Support Forum, etc.) since $submenu[ $menu_slug ] will be empty if the user doesn't have permission.
9255
			 *
9256
			 * In case a plugin does not have submenus under any of the default menus but does have submenus under the menu
9257
			 * of another plugin, only users that have the right role can access its sub-submenus since we will use the
9258
			 * capability needed to access the parent menu as the capability for the submenus that we will add.
9259
			 */
9260
			if ( empty( $submenu[ $menu_slug ] ) ) {
9261
				return;
9262
			}
9263
9264
			$top_level_menu = &$submenu[ $menu_slug ];
9265
9266
			$all_submenu_items_after = array();
9267
9268
			$found_submenu_item = false;
9269
9270
			foreach ( $top_level_menu as $submenu_id => $meta ) {
9271
				if ( $found_submenu_item ) {
9272
					// Remove all submenu items after the plugin's submenu item.
9273
					$all_submenu_items_after[] = $meta;
9274
					unset( $top_level_menu[ $submenu_id ] );
9275
				}
9276
9277
				if ( $this->_menu->get_raw_slug() === $meta[2] ) {
9278
					// Found the submenu item, put all below.
9279
					$found_submenu_item = true;
9280
					continue;
9281
				}
9282
			}
9283
9284
			// Embed all plugin's new submenu items.
9285
			$this->embed_submenu_items();
9286
9287
			// Start with specially high number to make sure it's appended.
9288
			$i = max( 10000, max( array_keys( $top_level_menu ) ) + 1 );
9289
			foreach ( $all_submenu_items_after as $meta ) {
9290
				$top_level_menu[ $i ] = $meta;
9291
				$i ++;
9292
			}
9293
9294
			// Sort submenu items.
9295
			ksort( $top_level_menu );
9296
		}
9297
9298
		/**
9299
		 * Helper method to return the module's support forum URL.
9300
		 *
9301
		 * @author Vova Feldman (@svovaf)
9302
		 * @since  1.2.2.7
9303
		 *
9304
		 * @return string
9305
		 */
9306
		function get_support_forum_url() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9307
			return $this->apply_filters( 'support_forum_url', "https://wordpress.org/support/{$this->_module_type}/{$this->_slug}" );
9308
		}
9309
9310
		/**
9311
		 * Displays the Support Forum link when enabled.
9312
		 *
9313
		 * Can be filtered like so:
9314
		 *
9315
		 *  function _fs_show_support_menu( $is_visible, $menu_id ) {
9316
		 *      if ( 'support' === $menu_id ) {
9317
		 *            return _fs->is_registered();
9318
		 *        }
9319
		 *        return $is_visible;
9320
		 *    }
9321
		 *    _fs()->add_filter('is_submenu_visible', '_fs_show_support_menu', 10, 2);
9322
		 *
9323
		 */
9324
		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...
9325
			if ( ! $this->is_on() ) {
9326
				return;
9327
			}
9328
9329
			if ( ! $this->is_activation_mode() ) {
9330
				$this->add_submenu_link_item(
9331
					$this->apply_filters( 'support_forum_submenu', $this->get_text( 'support-forum' ) ),
9332
					$this->get_support_forum_url(),
9333
					'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...
9334
					null,
9335
					50,
9336
					$this->is_submenu_item_visible( 'support' )
9337
				);
9338
			}
9339
		}
9340
9341
		/**
9342
		 * @author Vova Feldman (@svovaf)
9343
		 * @since  1.0.1
9344
		 *
9345
		 * @param string        $menu_title
9346
		 * @param callable      $render_function
9347
		 * @param bool|string   $page_title
9348
		 * @param string        $capability
9349
		 * @param bool|string   $menu_slug
9350
		 * @param bool|callable $before_render_function
9351
		 * @param int           $priority
9352
		 * @param bool          $show_submenu
9353
		 * @param string        $class Since 1.2.1.5 can add custom classes to menu items.
9354
		 */
9355
		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...
9356
			$menu_title,
9357
			$render_function,
9358
			$page_title = false,
9359
			$capability = 'manage_options',
9360
			$menu_slug = false,
9361
			$before_render_function = false,
9362
			$priority = WP_FS__DEFAULT_PRIORITY,
9363
			$show_submenu = true,
9364
			$class = ''
9365
		) {
9366
			$this->_logger->entrance( 'Title = ' . $menu_title );
9367
9368
			if ( $this->is_addon() ) {
9369
				$parent_fs = $this->get_parent_instance();
9370
9371
				if ( is_object( $parent_fs ) ) {
9372
					$parent_fs->add_submenu_item(
9373
						$menu_title,
9374
						$render_function,
9375
						$page_title,
9376
						$capability,
9377
						$menu_slug,
9378
						$before_render_function,
9379
						$priority,
9380
						$show_submenu,
9381
						$class
9382
					);
9383
9384
					return;
9385
				}
9386
			}
9387
9388
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
9389
				$this->_menu_items[ $priority ] = array();
9390
			}
9391
9392
			$this->_menu_items[ $priority ][] = array(
9393
				'page_title'             => is_string( $page_title ) ? $page_title : $menu_title,
9394
				'menu_title'             => $menu_title,
9395
				'capability'             => $capability,
9396
				'menu_slug'              => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ),
9397
				'render_function'        => $render_function,
9398
				'before_render_function' => $before_render_function,
9399
				'show_submenu'           => $show_submenu,
9400
				'class'                  => $class,
9401
			);
9402
		}
9403
9404
		/**
9405
		 * @author Vova Feldman (@svovaf)
9406
		 * @since  1.0.1
9407
		 *
9408
		 * @param string $menu_title
9409
		 * @param string $url
9410
		 * @param bool   $menu_slug
9411
		 * @param string $capability
9412
		 * @param int    $priority
9413
		 * @param bool   $show_submenu
9414
		 */
9415
		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...
9416
			$menu_title,
9417
			$url,
9418
			$menu_slug = false,
9419
			$capability = 'read',
9420
			$priority = WP_FS__DEFAULT_PRIORITY,
9421
			$show_submenu = true
9422
		) {
9423
			$this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url );
9424
9425
			if ( $this->is_addon() ) {
9426
				$parent_fs = $this->get_parent_instance();
9427
9428
				if ( is_object( $parent_fs ) ) {
9429
					$parent_fs->add_submenu_link_item(
9430
						$menu_title,
9431
						$url,
9432
						$menu_slug,
9433
						$capability,
9434
						$priority,
9435
						$show_submenu
9436
					);
9437
9438
					return;
9439
				}
9440
			}
9441
9442
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
9443
				$this->_menu_items[ $priority ] = array();
9444
			}
9445
9446
			$this->_menu_items[ $priority ][] = array(
9447
				'menu_title'             => $menu_title,
9448
				'capability'             => $capability,
9449
				'menu_slug'              => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ),
9450
				'url'                    => $url,
9451
				'page_title'             => $menu_title,
9452
				'render_function'        => 'fs_dummy',
9453
				'before_render_function' => '',
9454
				'show_submenu'           => $show_submenu,
9455
			);
9456
		}
9457
9458
		#endregion ------------------------------------------------------------------
9459
9460
9461
		#--------------------------------------------------------------------------------
9462
		#region Actions / Hooks / Filters
9463
		#--------------------------------------------------------------------------------
9464
9465
		/**
9466
		 * @author Vova Feldman (@svovaf)
9467
		 * @since  1.1.7
9468
		 *
9469
		 * @param string $tag
9470
		 *
9471
		 * @return string
9472
		 */
9473
		public function get_action_tag( $tag ) {
9474
			return self::get_action_tag_static( $tag, $this->_slug, $this->is_plugin() );
9475
		}
9476
9477
		/**
9478
		 * @author Vova Feldman (@svovaf)
9479
		 * @since  1.2.1.6
9480
		 *
9481
		 * @param string $tag
9482
		 * @param string $slug
9483
		 * @param bool   $is_plugin
9484
		 *
9485
		 * @return string
9486
		 */
9487
		static function get_action_tag_static( $tag, $slug = '', $is_plugin = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9488
			$action = "fs_{$tag}";
9489
9490
			if ( ! empty( $slug ) ) {
9491
				$action .= '_' . self::get_module_unique_affix( $slug, $is_plugin );
9492
			}
9493
9494
			return $action;
9495
		}
9496
9497
		/**
9498
		 * Returns a string that can be used to generate a unique action name,
9499
		 * option name, HTML element ID, or HTML element class.
9500
		 *
9501
		 * @author Leo Fajardo (@leorw)
9502
		 * @since  1.2.2
9503
		 *
9504
		 * @return string
9505
		 */
9506
		public function get_unique_affix() {
9507
			return self::get_module_unique_affix( $this->_slug, $this->is_plugin() );
9508
		}
9509
9510
		/**
9511
		 * Returns a string that can be used to generate a unique action name,
9512
		 * option name, HTML element ID, or HTML element class.
9513
		 *
9514
		 * @author Vova Feldman (@svovaf)
9515
		 * @since  1.2.2.5
9516
		 *
9517
		 * @param string $slug
9518
		 * @param bool   $is_plugin
9519
		 *
9520
		 * @return string
9521
		 */
9522
		static function get_module_unique_affix( $slug, $is_plugin = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
9523
			$affix = $slug;
9524
9525
			if ( ! $is_plugin ) {
9526
				$affix .= '-' . WP_FS__MODULE_TYPE_THEME;
9527
			}
9528
9529
			return $affix;
9530
		}
9531
9532
		/**
9533
		 * @author Vova Feldman (@svovaf)
9534
		 * @since  1.2.1
9535
		 * @since  1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are
9536
		 *         based on the slug for backward compatibility.
9537
		 *
9538
		 * @param string $tag
9539
		 *
9540
		 * @return string
9541
		 */
9542
		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...
9543
			return self::get_ajax_action_static( $tag, $this->_module_id );
9544
		}
9545
9546
		/**
9547
		 * @author Vova Feldman (@svovaf)
9548
		 * @since  1.2.1.7
9549
		 *
9550
		 * @param string $tag
9551
		 *
9552
		 * @return string
9553
		 */
9554
		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...
9555
			return wp_create_nonce( $this->get_ajax_action( $tag ) );
9556
		}
9557
9558
		/**
9559
		 * @author Vova Feldman (@svovaf)
9560
		 * @since  1.2.1.7
9561
		 *
9562
		 * @param string $tag
9563
		 */
9564
		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...
9565
			check_ajax_referer( $this->get_ajax_action( $tag ), 'security' );
9566
		}
9567
9568
		/**
9569
		 * @author Vova Feldman (@svovaf)
9570
		 * @since  1.2.1.6
9571
		 * @since  1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are
9572
		 *         based on the slug for backward compatibility.
9573
		 *
9574
		 * @param string      $tag
9575
		 * @param number|null $module_id
9576
		 *
9577
		 * @return string
9578
		 */
9579
		private static function get_ajax_action_static( $tag, $module_id = null ) {
9580
			$action = "fs_{$tag}";
9581
9582
			if ( ! empty( $module_id ) ) {
9583
				$action .= "_{$module_id}";
9584
			}
9585
9586
			return $action;
9587
		}
9588
9589
		/**
9590
		 * Do action, specific for the current context plugin.
9591
		 *
9592
		 * @author Vova Feldman (@svovaf)
9593
		 * @since  1.0.1
9594
		 *
9595
		 * @param string $tag     The name of the action to be executed.
9596
		 * @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...
9597
		 *                        functions hooked to the action. Default empty.
9598
		 *
9599
		 * @uses   do_action()
9600
		 */
9601
		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...
9602
			$this->_logger->entrance( $tag );
9603
9604
			$args = func_get_args();
9605
9606
			call_user_func_array( 'do_action', array_merge(
9607
					array( $this->get_action_tag( $tag ) ),
9608
					array_slice( $args, 1 ) )
9609
			);
9610
		}
9611
9612
		/**
9613
		 * Add action, specific for the current context plugin.
9614
		 *
9615
		 * @author Vova Feldman (@svovaf)
9616
		 * @since  1.0.1
9617
		 *
9618
		 * @param string   $tag
9619
		 * @param callable $function_to_add
9620
		 * @param int      $priority
9621
		 * @param int      $accepted_args
9622
		 *
9623
		 * @uses   add_action()
9624
		 */
9625
		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...
9626
			$tag,
9627
			$function_to_add,
9628
			$priority = WP_FS__DEFAULT_PRIORITY,
9629
			$accepted_args = 1
9630
		) {
9631
			$this->_logger->entrance( $tag );
9632
9633
			add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
9634
		}
9635
9636
		/**
9637
		 * Add AJAX action, specific for the current context plugin.
9638
		 *
9639
		 * @author Vova Feldman (@svovaf)
9640
		 * @since  1.2.1
9641
		 *
9642
		 * @param string   $tag
9643
		 * @param callable $function_to_add
9644
		 * @param int      $priority
9645
		 *
9646
		 * @uses   add_action()
9647
		 *
9648
		 * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
9649
		 */
9650
		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...
9651
			$tag,
9652
			$function_to_add,
9653
			$priority = WP_FS__DEFAULT_PRIORITY
9654
		) {
9655
			$this->_logger->entrance( $tag );
9656
9657
			return self::add_ajax_action_static(
9658
				$tag,
9659
				$function_to_add,
9660
				$priority,
9661
				$this->_module_id
9662
			);
9663
		}
9664
9665
		/**
9666
		 * Add AJAX action.
9667
		 *
9668
		 * @author Vova Feldman (@svovaf)
9669
		 * @since  1.2.1.6
9670
		 *
9671
		 * @param string      $tag
9672
		 * @param callable    $function_to_add
9673
		 * @param int         $priority
9674
		 * @param number|null $module_id
9675
		 *
9676
		 * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
9677
		 * @uses   add_action()
9678
		 *
9679
		 */
9680
		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...
9681
			$tag,
9682
			$function_to_add,
9683
			$priority = WP_FS__DEFAULT_PRIORITY,
9684
			$module_id = null
9685
		) {
9686
			self::$_static_logger->entrance( $tag );
9687
9688
			if ( ! self::is_ajax_action_static( $tag, $module_id ) ) {
9689
				return false;
9690
			}
9691
9692
			add_action(
9693
				'wp_ajax_' . self::get_ajax_action_static( $tag, $module_id ),
9694
				$function_to_add,
9695
				$priority,
9696
				0
9697
			);
9698
9699
			self::$_static_logger->info( "$tag AJAX callback action added." );
9700
9701
			return true;
9702
		}
9703
9704
		/**
9705
		 * Send a JSON response back to an Ajax request.
9706
		 *
9707
		 * @author Vova Feldman (@svovaf)
9708
		 * @since  1.2.1.5
9709
		 *
9710
		 * @param mixed $response
9711
		 */
9712
		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...
9713
			wp_send_json( $response );
9714
		}
9715
9716
		/**
9717
		 * Send a JSON response back to an Ajax request, indicating success.
9718
		 *
9719
		 * @author Vova Feldman (@svovaf)
9720
		 * @since  1.2.1.5
9721
		 *
9722
		 * @param mixed $data Data to encode as JSON, then print and exit.
9723
		 */
9724
		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...
9725
			wp_send_json_success( $data );
9726
		}
9727
9728
		/**
9729
		 * Send a JSON response back to an Ajax request, indicating failure.
9730
		 *
9731
		 * @author Vova Feldman (@svovaf)
9732
		 * @since  1.2.1.5
9733
		 *
9734
		 * @param mixed $error Optional error message.
9735
		 */
9736
		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...
9737
			$result = array( 'success' => false );
9738
			if ( ! empty( $error ) ) {
9739
				$result['error'] = $error;
9740
			}
9741
9742
			wp_send_json( $result );
9743
		}
9744
9745
		/**
9746
		 * Apply filter, specific for the current context plugin.
9747
		 *
9748
		 * @author Vova Feldman (@svovaf)
9749
		 * @since  1.0.9
9750
		 *
9751
		 * @param string $tag   The name of the filter hook.
9752
		 * @param mixed  $value The value on which the filters hooked to `$tag` are applied on.
9753
		 *
9754
		 * @return mixed The filtered value after all hooked functions are applied to it.
9755
		 *
9756
		 * @uses   apply_filters()
9757
		 */
9758
		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...
9759
			$this->_logger->entrance( $tag );
9760
9761
			$args = func_get_args();
9762
			array_unshift( $args, $this->get_unique_affix() );
9763
9764
			return call_user_func_array( 'fs_apply_filter', $args );
9765
		}
9766
9767
		/**
9768
		 * Add filter, specific for the current context plugin.
9769
		 *
9770
		 * @author Vova Feldman (@svovaf)
9771
		 * @since  1.0.9
9772
		 *
9773
		 * @param string   $tag
9774
		 * @param callable $function_to_add
9775
		 * @param int      $priority
9776
		 * @param int      $accepted_args
9777
		 *
9778
		 * @uses   add_filter()
9779
		 */
9780
		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...
9781
			$this->_logger->entrance( $tag );
9782
9783
			add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
9784
		}
9785
9786
		/**
9787
		 * Check if has filter.
9788
		 *
9789
		 * @author Vova Feldman (@svovaf)
9790
		 * @since  1.1.4
9791
		 *
9792
		 * @param string        $tag
9793
		 * @param callable|bool $function_to_check Optional. The callback to check for. Default false.
9794
		 *
9795
		 * @return false|int
9796
		 *
9797
		 * @uses   has_filter()
9798
		 */
9799
		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...
9800
			$this->_logger->entrance( $tag );
9801
9802
			return has_filter( $this->get_action_tag( $tag ), $function_to_check );
9803
		}
9804
9805
		#endregion
9806
9807
		/**
9808
		 * Override default i18n text phrases.
9809
		 *
9810
		 * @author Vova Feldman (@svovaf)
9811
		 * @since  1.1.6
9812
		 *
9813
		 * @param string[] string $key_value
9814
		 *
9815
		 * @uses   fs_override_i18n()
9816
		 */
9817
		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...
9818
			fs_override_i18n( $key_value, $this->_slug );
9819
		}
9820
9821
		/* Account Page
9822
		------------------------------------------------------------------------------------------------------------------*/
9823
		/**
9824
		 * Update site information.
9825
		 *
9826
		 * @author Vova Feldman (@svovaf)
9827
		 * @since  1.0.1
9828
		 *
9829
		 * @param bool $store Flush to Database if true.
9830
		 */
9831
		private function _store_site( $store = true ) {
9832
			$this->_logger->entrance();
9833
9834
			if ( empty( $this->_site->id ) ) {
9835
				$this->_logger->error( "Empty install ID, can't store site." );
9836
9837
				return;
9838
			}
9839
9840
			$encrypted_site       = clone $this->_site;
9841
			$encrypted_site->plan = self::_encrypt_entity( $this->_site->plan );
9842
9843
			$sites                 = self::get_all_sites( $this->_module_type );
9844
			$sites[ $this->_slug ] = $encrypted_site;
9845
9846
			$this->set_account_option( 'sites', $sites, $store );
9847
		}
9848
9849
		/**
9850
		 * Update plugin's plans information.
9851
		 *
9852
		 * @author Vova Feldman (@svovaf)
9853
		 * @since  1.0.2
9854
		 *
9855
		 * @param bool $store Flush to Database if true.
9856
		 */
9857
		private function _store_plans( $store = true ) {
9858
			$this->_logger->entrance();
9859
9860
			$plans = self::get_all_plans( $this->_module_type );
9861
9862
			// Copy plans.
9863
			$encrypted_plans = array();
9864
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
9865
				$encrypted_plans[] = self::_encrypt_entity( $this->_plans[ $i ] );
9866
			}
9867
9868
			$plans[ $this->_slug ] = $encrypted_plans;
9869
9870
			$this->set_account_option( 'plans', $plans, $store );
9871
		}
9872
9873
		/**
9874
		 * Update user's plugin licenses.
9875
		 *
9876
		 * @author Vova Feldman (@svovaf)
9877
		 * @since  1.0.5
9878
		 *
9879
		 * @param bool                $store
9880
		 * @param string|bool         $plugin_slug
9881
		 * @param FS_Plugin_License[] $licenses
9882
		 */
9883
		private function _store_licenses( $store = true, $plugin_slug = false, $licenses = array() ) {
9884
			$this->_logger->entrance();
9885
9886
			$all_licenses = self::get_all_licenses( $this->_module_type );
9887
9888
			if ( ! is_string( $plugin_slug ) ) {
9889
				$plugin_slug = $this->_slug;
9890
				$licenses    = $this->_licenses;
9891
			}
9892
9893
			if ( ! isset( $all_licenses[ $plugin_slug ] ) ) {
9894
				$all_licenses[ $plugin_slug ] = array();
9895
			}
9896
9897
			$all_licenses[ $plugin_slug ][ $this->_user->id ] = $licenses;
9898
9899
			$this->set_account_option( 'licenses', $all_licenses, $store );
9900
		}
9901
9902
		/**
9903
		 * Update user information.
9904
		 *
9905
		 * @author Vova Feldman (@svovaf)
9906
		 * @since  1.0.1
9907
		 *
9908
		 * @param bool $store Flush to Database if true.
9909
		 */
9910
		private function _store_user( $store = true ) {
9911
			$this->_logger->entrance();
9912
9913
			if ( empty( $this->_user->id ) ) {
9914
				$this->_logger->error( "Empty user ID, can't store user." );
9915
9916
				return;
9917
			}
9918
9919
			$users                     = self::get_all_users();
9920
			$users[ $this->_user->id ] = $this->_user;
9921
			self::$_accounts->set_option( 'users', $users, $store );
9922
		}
9923
9924
		/**
9925
		 * Update new updates information.
9926
		 *
9927
		 * @author Vova Feldman (@svovaf)
9928
		 * @since  1.0.4
9929
		 *
9930
		 * @param FS_Plugin_Tag|null $update
9931
		 * @param bool               $store Flush to Database if true.
9932
		 * @param bool|number        $plugin_id
9933
		 */
9934
		private function _store_update( $update, $store = true, $plugin_id = false ) {
9935
			$this->_logger->entrance();
9936
9937
			if ( $update instanceof FS_Plugin_Tag ) {
9938
				$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...
9939
			}
9940
9941
			if ( ! is_numeric( $plugin_id ) ) {
9942
				$plugin_id = $this->_plugin->id;
9943
			}
9944
9945
			$updates               = self::get_all_updates();
9946
			$updates[ $plugin_id ] = $update;
9947
			self::$_accounts->set_option( 'updates', $updates, $store );
9948
		}
9949
9950
		/**
9951
		 * Update new updates information.
9952
		 *
9953
		 * @author   Vova Feldman (@svovaf)
9954
		 * @since    1.0.6
9955
		 *
9956
		 * @param FS_Plugin[] $plugin_addons
9957
		 * @param bool        $store Flush to Database if true.
9958
		 */
9959
		private function _store_addons( $plugin_addons, $store = true ) {
9960
			$this->_logger->entrance();
9961
9962
			$addons                       = self::get_all_addons();
9963
			$addons[ $this->_plugin->id ] = $plugin_addons;
9964
			self::$_accounts->set_option( 'addons', $addons, $store );
9965
		}
9966
9967
		/**
9968
		 * Delete plugin's associated add-ons.
9969
		 *
9970
		 * @author   Vova Feldman (@svovaf)
9971
		 * @since    1.0.8
9972
		 *
9973
		 * @param bool $store
9974
		 *
9975
		 * @return bool
9976
		 */
9977
		private function _delete_account_addons( $store = true ) {
9978
			$all_addons = self::get_all_account_addons();
9979
9980
			if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) {
9981
				return false;
9982
			}
9983
9984
			unset( $all_addons[ $this->_plugin->id ] );
9985
9986
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
9987
9988
			return true;
9989
		}
9990
9991
		/**
9992
		 * Update account add-ons list.
9993
		 *
9994
		 * @author   Vova Feldman (@svovaf)
9995
		 * @since    1.0.6
9996
		 *
9997
		 * @param FS_Plugin[] $addons
9998
		 * @param bool        $store Flush to Database if true.
9999
		 */
10000
		private function _store_account_addons( $addons, $store = true ) {
10001
			$this->_logger->entrance();
10002
10003
			$all_addons                       = self::get_all_account_addons();
10004
			$all_addons[ $this->_plugin->id ] = $addons;
10005
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
10006
		}
10007
10008
		/**
10009
		 * Store account params in the Database.
10010
		 *
10011
		 * @author Vova Feldman (@svovaf)
10012
		 * @since  1.0.1
10013
		 */
10014
		private function _store_account() {
10015
			$this->_logger->entrance();
10016
10017
			$this->_store_site( false );
10018
			$this->_store_user( false );
10019
			$this->_store_plans( false );
10020
			$this->_store_licenses( false );
10021
10022
			self::$_accounts->store();
10023
		}
10024
10025
		/**
10026
		 * Sync user's information.
10027
		 *
10028
		 * @author Vova Feldman (@svovaf)
10029
		 * @since  1.0.3
10030
		 * @uses   FS_Api
10031
		 */
10032
		private function _handle_account_user_sync() {
10033
			$this->_logger->entrance();
10034
10035
			$api = $this->get_api_user_scope();
10036
10037
			// Get user's information.
10038
			$user = $api->get( '/', true );
10039
10040
			if ( isset( $user->id ) ) {
10041
				$this->_user->first = $user->first;
10042
				$this->_user->last  = $user->last;
10043
				$this->_user->email = $user->email;
10044
10045
				$is_menu_item_account_visible = $this->is_submenu_item_visible( 'account' );
10046
10047
				if ( $user->is_verified &&
10048
				     ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified )
10049
				) {
10050
					$this->_user->is_verified = true;
10051
10052
					$this->do_action( 'account_email_verified', $user->email );
10053
10054
					$this->_admin_notices->add(
10055
						$this->get_text( 'email-verified-message' ),
10056
						$this->get_text( 'right-on' ) . '!',
10057
						'success',
10058
						// Make admin sticky if account menu item is invisible,
10059
						// since the page will be auto redirected to the plugin's
10060
						// main settings page, and the non-sticky message
10061
						// will disappear.
10062
						! $is_menu_item_account_visible,
10063
						false,
10064
						'email_verified'
10065
					);
10066
				}
10067
10068
				// Flush user details to DB.
10069
				$this->_store_user();
10070
10071
				$this->do_action( 'after_account_user_sync', $user );
10072
10073
				/**
10074
				 * If account menu item is hidden, redirect to plugin's main settings page.
10075
				 *
10076
				 * @author Vova Feldman (@svovaf)
10077
				 * @since  1.1.6
10078
				 *
10079
				 * @link   https://github.com/Freemius/wordpress-sdk/issues/6
10080
				 */
10081
				if ( ! $is_menu_item_account_visible ) {
10082
					fs_redirect( $this->_get_admin_page_url() );
10083
				}
10084
			}
10085
		}
10086
10087
		/**
10088
		 * @param bool $store
10089
		 *
10090
		 * @return FS_Plugin_Plan|object|false
10091
		 */
10092
		private function _enrich_site_plan( $store = true ) {
10093
			// Try to load plan from local cache.
10094
			$plan = $this->_get_plan_by_id( $this->_site->plan->id );
10095
10096
			if ( false === $plan ) {
10097
				$plan = $this->_fetch_site_plan();
10098
			}
10099
10100
			if ( $plan instanceof FS_Plugin_Plan ) {
10101
				$this->_update_plan( $plan, $store );
10102
			}
10103
10104
			return $plan;
10105
		}
10106
10107
		/**
10108
		 * @author Vova Feldman (@svovaf)
10109
		 * @since  1.0.9
10110
		 * @uses   FS_Api
10111
		 *
10112
		 * @param bool $store
10113
		 *
10114
		 * @return FS_Plugin_Plan|object|false
10115
		 */
10116
		private function _enrich_site_trial_plan( $store = true ) {
10117
			// Try to load plan from local cache.
10118
			$trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id );
10119
10120
			if ( false === $trial_plan ) {
10121
				$trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id );
10122
			}
10123
10124
			if ( $trial_plan instanceof FS_Plugin_Plan ) {
10125
				$this->_storage->store( 'trial_plan', $trial_plan, $store );
10126
			}
10127
10128
			return $trial_plan;
10129
		}
10130
10131
		/**
10132
		 * @author Vova Feldman (@svovaf)
10133
		 * @since  1.0.9
10134
		 * @uses   FS_Api
10135
		 *
10136
		 * @param number|bool $license_id
10137
		 *
10138
		 * @return FS_Subscription|object|bool
10139
		 */
10140
		private function _fetch_site_license_subscription( $license_id = false ) {
10141
			$this->_logger->entrance();
10142
			$api = $this->get_api_site_scope();
10143
10144
			if ( ! is_numeric( $license_id ) ) {
10145
				$license_id = $this->_license->id;
10146
			}
10147
10148
			$result = $api->get( "/licenses/{$license_id}/subscriptions.json", true );
10149
10150
			return ! isset( $result->error ) ?
10151
				( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ?
10152
					new FS_Subscription( $result->subscriptions[0] ) :
10153
					false
10154
				) :
10155
				$result;
10156
		}
10157
10158
		/**
10159
		 * @author Vova Feldman (@svovaf)
10160
		 * @since  1.0.4
10161
		 * @uses   FS_Api
10162
		 *
10163
		 * @param number|bool $plan_id
10164
		 *
10165
		 * @return FS_Plugin_Plan|object
10166
		 */
10167
		private function _fetch_site_plan( $plan_id = false ) {
10168
			$this->_logger->entrance();
10169
			$api = $this->get_api_site_scope();
10170
10171
			if ( ! is_numeric( $plan_id ) ) {
10172
				$plan_id = $this->_site->plan->id;
10173
			}
10174
10175
			$plan = $api->get( "/plans/{$plan_id}.json", true );
10176
10177
			return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan;
10178
		}
10179
10180
		/**
10181
		 * @author Vova Feldman (@svovaf)
10182
		 * @since  1.0.5
10183
		 * @uses   FS_Api
10184
		 *
10185
		 * @return FS_Plugin_Plan[]|object
10186
		 */
10187
		private function _fetch_plugin_plans() {
10188
			$this->_logger->entrance();
10189
			$api = $this->get_api_site_scope();
10190
10191
			$result = $api->get( '/plans.json', true );
10192
10193
			if ( $this->is_api_result_object( $result, 'plans' ) && is_array( $result->plans ) ) {
10194
				for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) {
10195
					$result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] );
10196
				}
10197
10198
				$result = $result->plans;
10199
			}
10200
10201
			return $result;
10202
		}
10203
10204
		/**
10205
		 * @author Vova Feldman (@svovaf)
10206
		 * @since  1.0.5
10207
		 * @uses   FS_Api
10208
		 *
10209
		 * @param number|bool $plugin_id
10210
		 * @param number|bool $site_license_id
10211
		 *
10212
		 * @return FS_Plugin_License[]|object
10213
		 */
10214
		private function _fetch_licenses( $plugin_id = false, $site_license_id = false ) {
10215
			$this->_logger->entrance();
10216
10217
			$api = $this->get_api_user_scope();
10218
10219
			if ( ! is_numeric( $plugin_id ) ) {
10220
				$plugin_id = $this->_plugin->id;
10221
			}
10222
10223
			$result = $api->get( "/plugins/{$plugin_id}/licenses.json", true );
10224
10225
			$is_site_license_synced = false;
10226
10227
			$api_errors = array();
10228
10229
			if ( $this->is_api_result_object( $result, 'licenses' ) &&
10230
			     is_array( $result->licenses )
10231
			) {
10232
				for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) {
10233
					$result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] );
10234
10235
					if ( ( ! $is_site_license_synced ) && is_numeric( $site_license_id ) ) {
10236
						$is_site_license_synced = ( $site_license_id == $result->licenses[ $i ]->id );
10237
					}
10238
				}
10239
10240
				$result = $result->licenses;
10241
			} else {
10242
				$api_errors[] = $result;
10243
				$result       = array();
10244
			}
10245
10246
			if ( ! $is_site_license_synced ) {
10247
				$api = $this->get_api_site_scope();
10248
10249
				if ( is_numeric( $site_license_id ) ) {
10250
					// Try to retrieve a foreign license that is linked to the install.
10251
					$api_result = $api->call( '/licenses.json' );
10252
10253
					if ( $this->is_api_result_object( $api_result, 'licenses' ) &&
10254
					     is_array( $api_result->licenses )
10255
					) {
10256
						$licenses = $api_result->licenses;
10257
10258
						if ( ! empty( $licenses ) ) {
10259
							$result[] = new FS_Plugin_License( $licenses[0] );
10260
						}
10261
					} else {
10262
						$api_errors[] = $api_result;
10263
					}
10264
				} else if ( is_object( $this->_license ) ) {
10265
					// Fetch foreign license by ID and license key.
10266
					$license = $api->get( "/licenses/{$this->_license->id}.json?license_key=" .
10267
					                      urlencode( $this->_license->secret_key ) );
10268
10269
					if ( $this->is_api_result_entity( $license ) ) {
10270
						$result[] = new FS_Plugin_License( $license );
10271
					} else {
10272
						$api_errors[] = $license;
10273
					}
10274
				}
10275
			}
10276
10277
			if ( is_array( $result ) && 0 < count( $result ) ) {
10278
				// If found at least one license, return license collection even if there are errors.
10279
				return $result;
10280
			}
10281
10282
			if ( ! empty( $api_errors ) ) {
10283
				// If found any errors and no licenses, return first error.
10284
				return $api_errors[0];
10285
			}
10286
10287
			// Fallback to empty licenses list.
10288
			return $result;
10289
		}
10290
10291
		/**
10292
		 * @author Vova Feldman (@svovaf)
10293
		 * @since  1.2.0
10294
		 * @uses   FS_Api
10295
		 *
10296
		 * @param number|bool $plugin_id
10297
		 * @param bool        $flush
10298
		 *
10299
		 * @return FS_Payment[]|object
10300
		 */
10301
		function _fetch_payments( $plugin_id = false, $flush = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10302
			$this->_logger->entrance();
10303
10304
			$api = $this->get_api_user_scope();
10305
10306
			if ( ! is_numeric( $plugin_id ) ) {
10307
				$plugin_id = $this->_plugin->id;
10308
			}
10309
10310
			$result = $api->get( "/plugins/{$plugin_id}/payments.json?include_addons=true", $flush );
10311
10312
			if ( ! isset( $result->error ) ) {
10313
				for ( $i = 0, $len = count( $result->payments ); $i < $len; $i ++ ) {
10314
					$result->payments[ $i ] = new FS_Payment( $result->payments[ $i ] );
10315
				}
10316
				$result = $result->payments;
10317
			}
10318
10319
			return $result;
10320
		}
10321
10322
		/**
10323
		 * @author Vova Feldman (@svovaf)
10324
		 * @since  1.2.1.5
10325
		 * @uses   FS_Api
10326
		 *
10327
		 * @param bool $flush
10328
		 *
10329
		 * @return \FS_Billing|mixed
10330
		 */
10331
		function _fetch_billing( $flush = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10332
			require_once WP_FS__DIR_INCLUDES . '/entities/class-fs-billing.php';
10333
10334
			$billing = $this->get_api_user_scope()->get( 'billing.json', $flush );
10335
10336
			if ( $this->is_api_result_entity( $billing ) ) {
10337
				$billing = new FS_Billing( $billing );
10338
			}
10339
10340
			return $billing;
10341
		}
10342
10343
		/**
10344
		 * @author Vova Feldman (@svovaf)
10345
		 * @since  1.0.4
10346
		 *
10347
		 * @param FS_Plugin_Plan $plan
10348
		 * @param bool           $store
10349
		 */
10350
		private function _update_plan( $plan, $store = false ) {
10351
			$this->_logger->entrance();
10352
10353
			$this->_site->plan = $plan;
10354
			$this->_store_site( $store );
10355
		}
10356
10357
		/**
10358
		 * @author Vova Feldman (@svovaf)
10359
		 * @since  1.0.5
10360
		 *
10361
		 * @param FS_Plugin_License[] $licenses
10362
		 * @param string|bool         $plugin_slug
10363
		 */
10364
		private function _update_licenses( $licenses, $plugin_slug = false ) {
10365
			$this->_logger->entrance();
10366
10367
			if ( is_array( $licenses ) ) {
10368
				for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) {
10369
					$licenses[ $i ]->updated = time();
10370
				}
10371
			}
10372
10373
			if ( ! is_string( $plugin_slug ) ) {
10374
				$this->_licenses = $licenses;
10375
			}
10376
10377
			$this->_store_licenses( true, $plugin_slug, $licenses );
10378
		}
10379
10380
		/**
10381
		 * @author Vova Feldman (@svovaf)
10382
		 * @since  1.0.4
10383
		 *
10384
		 * @param bool|number $plugin_id
10385
		 * @param bool        $flush Since 1.1.7.3
10386
		 * @param int         $expiration Since 1.2.2.7
10387
		 *
10388
		 * @return object|false New plugin tag info if exist.
10389
		 */
10390
		private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
10391
			$latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration );
10392
10393
			if ( ! is_object( $latest_tag ) ) {
10394
				return false;
10395
			}
10396
10397
			// Check if version is actually newer.
10398
			$has_new_version =
10399
				// If it's an non-installed add-on then always return latest.
10400
				( $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 10390 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...
10401
				// Compare versions.
10402
				version_compare( $this->get_plugin_version(), $latest_tag->version, '<' );
10403
10404
			$this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' );
10405
10406
			return $has_new_version ? $latest_tag : false;
10407
		}
10408
10409
		/**
10410
		 * @author Vova Feldman (@svovaf)
10411
		 * @since  1.0.5
10412
		 *
10413
		 * @param bool|number $plugin_id
10414
		 * @param bool        $flush      Since 1.1.7.3
10415
		 * @param int         $expiration Since 1.2.2.7
10416
		 *
10417
		 * @return bool|FS_Plugin_Tag
10418
		 */
10419
		function get_update( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
10420
			$this->_logger->entrance();
10421
10422
			if ( ! is_numeric( $plugin_id ) ) {
10423
				$plugin_id = $this->_plugin->id;
10424
			}
10425
10426
			$this->check_updates( true, $plugin_id, $flush, $expiration );
10427
			$updates = $this->get_all_updates();
10428
10429
			return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
10430
		}
10431
10432
		/**
10433
		 * Check if site assigned with active license.
10434
		 *
10435
		 * @author     Vova Feldman (@svovaf)
10436
		 * @since      1.0.6
10437
		 *
10438
		 * @deprecated Please use has_active_valid_license() instead because license can be cancelled.
10439
		 */
10440
		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...
10441
			return (
10442
				is_object( $this->_license ) &&
10443
				is_numeric( $this->_license->id ) &&
10444
				! $this->_license->is_expired()
10445
			);
10446
		}
10447
10448
		/**
10449
		 * Check if site assigned with active & valid (not expired) license.
10450
		 *
10451
		 * @author Vova Feldman (@svovaf)
10452
		 * @since  1.2.1
10453
		 */
10454
		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...
10455
			return (
10456
				is_object( $this->_license ) &&
10457
				is_numeric( $this->_license->id ) &&
10458
				$this->_license->is_active() &&
10459
				$this->_license->is_valid()
10460
			);
10461
		}
10462
10463
		/**
10464
		 * Check if site assigned with license with enabled features.
10465
		 *
10466
		 * @author Vova Feldman (@svovaf)
10467
		 * @since  1.0.6
10468
		 *
10469
		 * @return bool
10470
		 */
10471
		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...
10472
			return (
10473
				is_object( $this->_license ) &&
10474
				is_numeric( $this->_license->id ) &&
10475
				$this->_license->is_features_enabled()
10476
			);
10477
		}
10478
10479
		/**
10480
		 * Check if user is a trial or have feature enabled license.
10481
		 *
10482
		 * @author Vova Feldman (@svovaf)
10483
		 * @since  1.1.7
10484
		 *
10485
		 * @return bool
10486
		 */
10487
		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...
10488
			return $this->is_trial() || $this->has_features_enabled_license();
10489
		}
10490
10491
		/**
10492
		 * Checks if the current user can activate plugins or switch themes. Note that this method should only be used
10493
		 * after the `init` action is triggered because it is using `current_user_can()` which is only functional after
10494
		 * the context user is authenticated.
10495
		 *
10496
		 * @author Leo Fajardo (@leorw)
10497
		 * @since  1.2.2
10498
		 *
10499
		 * @return bool
10500
		 */
10501
		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...
10502
			return ( $this->is_plugin() && current_user_can( 'activate_plugins' ) )
10503
			       || ( $this->is_theme() && current_user_can( 'switch_themes' ) );
10504
		}
10505
10506
		/**
10507
		 * Sync site's plan.
10508
		 *
10509
		 * @author Vova Feldman (@svovaf)
10510
		 * @since  1.0.3
10511
		 *
10512
		 * @uses   FS_Api
10513
		 *
10514
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
10515
		 *                         the admin.
10516
		 */
10517
		private function _sync_license( $background = false ) {
10518
			$this->_logger->entrance();
10519
10520
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
10521
10522
			$is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() );
10523
10524
			if ( $is_addon_sync ) {
10525
				$this->_sync_addon_license( $plugin_id, $background );
10526
			} else {
10527
				$this->_sync_plugin_license( $background );
10528
			}
10529
10530
			$this->do_action( 'after_account_plan_sync', $this->_site->plan->name );
10531
		}
10532
10533
		/**
10534
		 * Sync plugin's add-on license.
10535
		 *
10536
		 * @author Vova Feldman (@svovaf)
10537
		 * @since  1.0.6
10538
		 * @uses   FS_Api
10539
		 *
10540
		 * @param number $addon_id
10541
		 * @param bool   $background
10542
		 */
10543
		private function _sync_addon_license( $addon_id, $background ) {
10544
			$this->_logger->entrance();
10545
10546
			if ( $this->is_addon_activated( $addon_id ) ) {
10547
				// If already installed, use add-on sync.
10548
				$fs_addon = self::get_instance_by_id( $addon_id );
10549
				$fs_addon->_sync_license( $background );
10550
10551
				return;
10552
			}
10553
10554
			// Validate add-on exists.
10555
			$addon = $this->get_addon( $addon_id );
10556
10557
			if ( ! is_object( $addon ) ) {
10558
				return;
10559
			}
10560
10561
			// Add add-on into account add-ons.
10562
			$account_addons = $this->get_account_addons();
10563
			if ( ! is_array( $account_addons ) ) {
10564
				$account_addons = array();
10565
			}
10566
			$account_addons[] = $addon->id;
10567
			$account_addons   = array_unique( $account_addons );
10568
			$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...
10569
10570
			// Load add-on licenses.
10571
			$licenses = $this->_fetch_licenses( $addon->id );
10572
10573
			// Sync add-on licenses.
10574
			if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) {
10575
				$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 10571 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...
10576
10577
				if ( ! $this->is_addon_installed( $addon->id ) && FS_License_Manager::has_premium_license( $licenses ) ) {
0 ignored issues
show
Bug introduced by
It seems like $licenses defined by $this->_fetch_licenses($addon->id) on line 10571 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...
10578
					$plans_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$addon_id}/plans.json" );
10579
10580
					if ( ! isset( $plans_result->error ) ) {
10581
						$plans = array();
10582
						foreach ( $plans_result->plans as $plan ) {
10583
							$plans[] = new FS_Plugin_Plan( $plan );
10584
						}
10585
10586
						$this->_admin_notices->add_sticky(
10587
							FS_Plan_Manager::instance()->has_free_plan( $plans ) ?
10588
								sprintf(
10589
									$this->get_text( 'addon-successfully-upgraded-message' ),
10590
									$addon->title
10591
								) . ' ' . $this->get_latest_download_link(
10592
									$this->get_text( 'download-latest-version' ),
10593
									$addon_id
10594
								)
10595
								:
10596
								sprintf(
10597
									$this->get_text( 'addon-successfully-purchased-message' ),
10598
									$addon->title
10599
								) . ' ' . $this->get_latest_download_link(
10600
									$this->get_text( 'download-latest-version' ),
10601
									$addon_id
10602
								),
10603
							'addon_plan_upgraded_' . $addon->slug,
10604
							$this->get_text( 'yee-haw' ) . '!'
10605
						);
10606
					}
10607
				}
10608
			}
10609
		}
10610
10611
		/**
10612
		 * Sync site's plugin plan.
10613
		 *
10614
		 * @author Vova Feldman (@svovaf)
10615
		 * @since  1.0.6
10616
		 * @uses   FS_Api
10617
		 *
10618
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
10619
		 *                         the admin.
10620
		 */
10621
		private function _sync_plugin_license( $background = false ) {
10622
			$this->_logger->entrance();
10623
10624
			/**
10625
			 * Sync site info.
10626
			 *
10627
			 * @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.
10628
			 */
10629
			$site = $this->send_install_update( array(), true );
10630
10631
			$plan_change = 'none';
10632
10633
			if ( ! $this->is_api_result_entity( $site ) ) {
10634
				// Show API messages only if not background sync or if paying customer.
10635
				if ( ! $background || $this->is_paying() ) {
10636
					// Try to ping API to see if not blocked.
10637
					if ( ! FS_Api::test() ) {
10638
						/**
10639
						 * Failed to ping API - blocked!
10640
						 *
10641
						 * @author Vova Feldman (@svovaf)
10642
						 * @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.
10643
						 */
10644
						$api = $this->get_api_site_scope();
10645
10646
						if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) {
10647
							self::$_global_admin_notices->add(
10648
								sprintf(
10649
									$this->get_text( 'server-blocking-access' ),
10650
									$this->get_plugin_name(),
10651
									'<a href="' . $api->get_url() . '" target="_blank">' . $api->get_url() . '</a>'
10652
								) . '<br> ' . $this->get_text( 'server-error-message' ) . var_export( $site->error, true ),
10653
								$this->get_text( 'oops' ) . '...',
10654
								'error',
10655
								$background,
10656
								false,
10657
								'api_blocked'
10658
							);
10659
						}
10660
					} else {
10661
						// Authentication params are broken.
10662
						$this->_admin_notices->add(
10663
							$this->get_text( 'wrong-authentication-param-message' ),
10664
							$this->get_text( 'oops' ) . '...',
10665
							'error'
10666
						);
10667
					}
10668
				}
10669
10670
				// No reason to continue with license sync while there are API issues.
10671
				return;
10672
			}
10673
10674
			// Remove sticky API connectivity message.
10675
			self::$_global_admin_notices->remove_sticky( 'api_blocked' );
10676
10677
			$site = new FS_Site( $site );
0 ignored issues
show
Bug introduced by
It seems like $site defined by new \FS_Site($site) on line 10677 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...
10678
10679
			// Sync plans.
10680
			$this->_sync_plans();
10681
10682
			if ( ! $this->has_paid_plan() ) {
10683
				$this->_site = $site;
10684
				$this->_enrich_site_plan( true );
10685
				$this->_store_site();
10686
			} else {
10687
				/**
10688
				 * Sync licenses. Pass the site's license ID so that the foreign licenses will be fetched if the license
10689
				 * associated with that ID is not included in the user's licenses collection.
10690
				 */
10691
				$this->_sync_licenses( $site->license_id );
10692
10693
				// Check if plan / license changed.
10694
				if ( ! FS_Entity::equals( $site->plan, $this->_site->plan ) ||
10695
				     // Check if trial started.
10696
				     $site->trial_plan_id != $this->_site->trial_plan_id ||
10697
				     $site->trial_ends != $this->_site->trial_ends ||
10698
				     // Check if license changed.
10699
				     $site->license_id != $this->_site->license_id
10700
				) {
10701
					if ( $site->is_trial() && ( ! $this->_site->is_trial() || $site->trial_ends != $this->_site->trial_ends ) ) {
10702
						// New trial started.
10703
						$this->_site = $site;
10704
						$plan_change = 'trial_started';
10705
10706
						// Store trial plan information.
10707
						$this->_enrich_site_trial_plan( true );
10708
10709
						// For trial with subscription use-case.
10710
						$new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id );
10711
10712
						if ( is_object( $new_license ) && $new_license->is_valid() ) {
10713
							$this->_site = $site;
10714
							$this->_update_site_license( $new_license );
10715
							$this->_store_licenses();
10716
							$this->_enrich_site_plan( true );
10717
10718
							$this->_sync_site_subscription( $this->_license );
10719
						}
10720
					} else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) {
10721
						// Was in trial, but now trial expired and no license ID.
10722
						// New trial started.
10723
						$this->_site = $site;
10724
						$plan_change = 'trial_expired';
10725
10726
						// Clear trial plan information.
10727
						$this->_storage->trial_plan = null;
10728
10729
					} else {
10730
						$is_free = $this->is_free_plan();
10731
10732
						// Make sure license exist and not expired.
10733
						$new_license = is_null( $site->license_id ) ?
10734
							null :
10735
							$this->_get_license_by_id( $site->license_id );
10736
10737
						if ( $is_free && is_null( $new_license ) && $this->has_any_license() && $this->_license->is_cancelled ) {
10738
							// License cancelled.
10739
							$this->_site = $site;
10740
							$this->_update_site_license( $new_license );
10741
							$this->_store_licenses();
10742
							$this->_enrich_site_plan( true );
10743
10744
							$plan_change = 'cancelled';
10745
						} 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...
10746
							// The license is expired, so ignore upgrade method.
10747
						} else {
10748
							// License changed.
10749
							$this->_site = $site;
10750
							$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 10733 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...
10751
							$this->_store_licenses();
10752
							$this->_enrich_site_plan( true );
10753
10754
							$plan_change = $is_free ?
10755
								'upgraded' :
10756
								( is_object( $new_license ) ?
10757
									'changed' :
10758
									'downgraded' );
10759
						}
10760
					}
10761
10762
					// Store updated site info.
10763
					$this->_store_site();
10764
				} else {
10765
					if ( is_object( $this->_license ) && $this->_license->is_expired() ) {
10766
						if ( ! $this->has_features_enabled_license() ) {
10767
							$this->_deactivate_license();
10768
							$plan_change = 'downgraded';
10769
						} else {
10770
							$plan_change = 'expired';
10771
						}
10772
					}
10773
10774
					if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) {
10775
						$this->_sync_site_subscription( $this->_license );
10776
					}
10777
				}
10778
			}
10779
10780
			if ( $this->has_paid_plan() ) {
10781
				switch ( $plan_change ) {
10782
					case 'none':
10783
						if ( ! $background && is_admin() ) {
10784
							$plan = $this->is_trial() ?
10785
								$this->_storage->trial_plan :
10786
								$this->_site->plan;
10787
10788
							if ( $plan->is_free() ) {
10789
								$this->_admin_notices->add(
10790
									sprintf(
10791
										$this->get_text( 'plan-did-not-change-message' ),
10792
										'<i><b>' . $plan->title . ( $this->is_trial() ? ' ' . $this->get_text( 'trial' ) : '' ) . '</b></i>'
10793
									) . ' ' . sprintf(
10794
										'<a href="%s">%s</a>',
10795
										$this->contact_url(
10796
											'bug',
10797
											sprintf( $this->get_text( 'plan-did-not-change-email-message' ),
10798
												strtoupper( $plan->name )
10799
											)
10800
										),
10801
										$this->get_text( 'contact-us-here' )
10802
									),
10803
									$this->get_text( 'hmm' ) . '...'
10804
								);
10805
							}
10806
						}
10807
						break;
10808
					case 'upgraded':
10809
						$this->_admin_notices->add_sticky(
10810
							sprintf(
10811
								$this->get_text( 'plan-upgraded-message' ),
10812
								'<i>' . $this->get_plugin_name() . '</i>'
10813
							) . $this->get_complete_upgrade_instructions(),
10814
							'plan_upgraded',
10815
							$this->get_text( 'yee-haw' ) . '!'
10816
						);
10817
10818
						$this->_admin_notices->remove_sticky( array(
10819
							'trial_started',
10820
							'trial_promotion',
10821
							'trial_expired',
10822
							'activation_complete',
10823
						) );
10824
						break;
10825
					case 'changed':
10826
						$this->_admin_notices->add_sticky(
10827
							sprintf(
10828
								$this->get_text( 'plan-changed-to-x-message' ),
10829
								$this->_site->plan->title
10830
							),
10831
							'plan_changed'
10832
						);
10833
10834
						$this->_admin_notices->remove_sticky( array(
10835
							'trial_started',
10836
							'trial_promotion',
10837
							'trial_expired',
10838
							'activation_complete',
10839
						) );
10840
						break;
10841
					case 'downgraded':
10842
						$this->_admin_notices->add_sticky(
10843
							sprintf( $this->get_text( 'license-expired-blocking-message' ), $this->_module_type ),
10844
							'license_expired',
10845
							$this->get_text( 'hmm' ) . '...'
10846
						);
10847
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
10848
						break;
10849
					case 'cancelled':
10850
						$this->_admin_notices->add(
10851
							$this->get_text( 'license-cancelled' ) . ' ' .
10852
							sprintf(
10853
								'<a href="%s">%s</a>',
10854
								$this->contact_url( 'bug' ),
10855
								$this->get_text( 'contact-us-here' )
10856
							),
10857
							$this->get_text( 'hmm' ) . '...',
10858
							'error'
10859
						);
10860
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
10861
						break;
10862
					case 'expired':
10863
						$this->_admin_notices->add_sticky(
10864
							sprintf( $this->get_text( 'license-expired-non-blocking-message' ), $this->_site->plan->title ),
10865
							'license_expired',
10866
							$this->get_text( 'hmm' ) . '...'
10867
						);
10868
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
10869
						break;
10870
					case 'trial_started':
10871
						$this->_admin_notices->add_sticky(
10872
							sprintf(
10873
								$this->get_text( 'trial-started-message' ),
10874
								'<i>' . $this->get_plugin_name() . '</i>'
10875
							) . $this->get_complete_upgrade_instructions( $this->_storage->trial_plan->title ),
10876
							'trial_started',
10877
							$this->get_text( 'yee-haw' ) . '!'
10878
						);
10879
10880
						$this->_admin_notices->remove_sticky( array(
10881
							'trial_promotion',
10882
						) );
10883
						break;
10884
					case 'trial_expired':
10885
						$this->_admin_notices->add_sticky(
10886
							$this->get_text( 'trial-expired-message' ),
10887
							'trial_expired',
10888
							$this->get_text( 'hmm' ) . '...'
10889
						);
10890
						$this->_admin_notices->remove_sticky( array(
10891
							'trial_started',
10892
							'trial_promotion',
10893
							'plan_upgraded',
10894
						) );
10895
						break;
10896
				}
10897
			}
10898
10899
			if ( 'none' !== $plan_change ) {
10900
				$this->do_action( 'after_license_change', $plan_change, $this->_site->plan );
10901
			}
10902
		}
10903
10904
		/**
10905
		 * @author Vova Feldman (@svovaf)
10906
		 * @since  1.0.5
10907
		 *
10908
		 * @param bool $background
10909
		 */
10910
		protected function _activate_license( $background = false ) {
10911
			$this->_logger->entrance();
10912
10913
			$license_id = fs_request_get( 'license_id' );
10914
10915
			if ( FS_Plugin_License::is_valid_id( $license_id ) && $license_id == $this->_site->license_id ) {
10916
				// License is already activated.
10917
				return;
10918
			}
10919
10920
			$premium_license = FS_Plugin_License::is_valid_id( $license_id ) ?
10921
				$this->_get_license_by_id( $license_id ) :
10922
				$this->_get_available_premium_license();
10923
10924
			if ( ! is_object( $premium_license ) ) {
10925
				return;
10926
			}
10927
10928
			/**
10929
			 * If the premium license is already associated with the install, just
10930
			 * update the license reference (activation is not required).
10931
			 *
10932
			 * @since 1.1.9
10933
			 */
10934
			if ( $premium_license->id == $this->_site->license_id ) {
10935
				// License is already activated.
10936
				$this->_update_site_license( $premium_license );
10937
				$this->_enrich_site_plan( false );
10938
				$this->_store_account();
10939
10940
				return;
10941
			}
10942
10943
			if ( $this->_site->user_id != $premium_license->user_id ) {
10944
				$api_request_params = array( 'license_key' => $premium_license->secret_key );
10945
			} else {
10946
				$api_request_params = array();
10947
			}
10948
10949
			$api     = $this->get_api_site_scope();
10950
			$license = $api->call( "/licenses/{$premium_license->id}.json", 'put', $api_request_params );
10951
10952
			if ( ! $this->is_api_result_entity( $license ) ) {
10953
				if ( ! $background ) {
10954
					$this->_admin_notices->add( sprintf(
10955
						'%s %s',
10956
						$this->get_text( 'license-activation-failed-message' ),
10957
						( is_object( $license ) && isset( $license->error ) ?
10958
							$license->error->message :
10959
							sprintf( '%s<br><code>%s</code>',
10960
								$this->get_text( 'server-error-message' ),
10961
								var_export( $license, true )
10962
							)
10963
						)
10964
					),
10965
						$this->get_text( 'hmm' ) . '...',
10966
						'error'
10967
					);
10968
				}
10969
10970
				return;
10971
			}
10972
10973
			$premium_license = new FS_Plugin_License( $license );
10974
10975
			// Updated site plan.
10976
			$site = $this->get_api_site_scope()->get( '/', true );
10977
			if ( $this->is_api_result_entity( $site ) ) {
10978
				$this->_site = new FS_Site( $site );
10979
			}
10980
			$this->_update_site_license( $premium_license );
10981
			$this->_enrich_site_plan( false );
10982
10983
			$this->_store_account();
10984
10985
			if ( ! $background ) {
10986
				$this->_admin_notices->add_sticky(
10987
					$this->get_text( 'license-activated-message' ) .
10988
					$this->get_complete_upgrade_instructions(),
10989
					'license_activated',
10990
					$this->get_text( 'yee-haw' ) . '!'
10991
				);
10992
			}
10993
10994
			$this->_admin_notices->remove_sticky( array(
10995
				'trial_promotion',
10996
				'license_expired',
10997
			) );
10998
		}
10999
11000
		/**
11001
		 * @author Vova Feldman (@svovaf)
11002
		 * @since  1.0.5
11003
		 *
11004
		 * @param bool $show_notice
11005
		 */
11006
		protected function _deactivate_license( $show_notice = true ) {
11007
			$this->_logger->entrance();
11008
11009
			if ( ! is_object( $this->_license ) ) {
11010
				$this->_admin_notices->add(
11011
					sprintf( $this->get_text( 'no-active-license-message' ), $this->_site->plan->title ),
11012
					$this->get_text( 'hmm' ) . '...'
11013
				);
11014
11015
				return;
11016
			}
11017
11018
			$api     = $this->get_api_site_scope();
11019
			$license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' );
11020
11021
			if ( isset( $license->error ) ) {
11022
				$this->_admin_notices->add(
11023
					$this->get_text( 'license-deactivation-failed-message' ) . '<br> ' .
11024
					$this->get_text( 'server-error-message' ) . ' ' . var_export( $license->error, true ),
11025
					$this->get_text( 'hmm' ) . '...',
11026
					'error'
11027
				);
11028
11029
				return;
11030
			}
11031
11032
			// Update license cache.
11033
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
11034
				if ( $license->id == $this->_licenses[ $i ]->id ) {
11035
					$this->_licenses[ $i ] = new FS_Plugin_License( $license );
11036
				}
11037
			}
11038
11039
			// Updated site plan to default.
11040
			$this->_sync_plans();
11041
			$this->_site->plan->id = $this->_plans[0]->id;
11042
			// Unlink license from site.
11043
			$this->_update_site_license( null );
11044
			$this->_enrich_site_plan( false );
11045
11046
			$this->_store_account();
11047
11048
			if ( $show_notice ) {
11049
				$this->_admin_notices->add(
11050
					sprintf( $this->get_text( 'license-deactivation-message' ), $this->_site->plan->title ),
11051
					$this->get_text( 'ok' )
11052
				);
11053
			}
11054
11055
			$this->_admin_notices->remove_sticky( array(
11056
				'plan_upgraded',
11057
				'license_activated',
11058
			) );
11059
		}
11060
11061
		/**
11062
		 * Site plan downgrade.
11063
		 *
11064
		 * @author Vova Feldman (@svovaf)
11065
		 * @since  1.0.4
11066
		 *
11067
		 * @uses   FS_Api
11068
		 */
11069
		private function _downgrade_site() {
11070
			$this->_logger->entrance();
11071
11072
			$api  = $this->get_api_site_scope();
11073
			$site = $api->call( 'downgrade.json', 'put' );
11074
11075
			$plan_downgraded = false;
11076
			$plan            = false;
11077
			if ( $this->is_api_result_entity( $site ) ) {
11078
				$prev_plan_id = $this->_site->plan->id;
11079
11080
				// Update new site plan id.
11081
				$this->_site->plan->id = $site->plan_id;
11082
11083
				$plan         = $this->_enrich_site_plan();
11084
				$subscription = $this->_sync_site_subscription( $this->_license );
11085
11086
				// Plan downgraded if plan was changed or subscription was cancelled.
11087
				$plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) ||
11088
				                   ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() );
11089
			} 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...
11090
				// handle different error cases.
11091
11092
			}
11093
11094
			if ( $plan_downgraded ) {
11095
				// Remove previous sticky message about upgrade (if exist).
11096
				$this->_admin_notices->remove_sticky( 'plan_upgraded' );
11097
11098
				$this->_admin_notices->add(
11099
					sprintf( $this->get_text( 'plan-x-downgraded-message' ),
11100
						$plan->title,
11101
						human_time_diff( time(), strtotime( $this->_license->expiration ) )
11102
					)
11103
				);
11104
11105
				// Store site updates.
11106
				$this->_store_site();
11107
			} else {
11108
				$this->_admin_notices->add(
11109
					$this->get_text( 'plan-downgraded-failure-message' ),
11110
					$this->get_text( 'oops' ) . '...',
11111
					'error'
11112
				);
11113
			}
11114
		}
11115
11116
		/**
11117
		 * @author Vova Feldman (@svovaf)
11118
		 * @since  1.1.8.1
11119
		 *
11120
		 * @param bool|string $plan_name
11121
		 *
11122
		 * @return bool If trial was successfully started.
11123
		 */
11124
		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...
11125
			$this->_logger->entrance();
11126
11127
			if ( $this->is_trial() ) {
11128
				// Already in trial mode.
11129
				$this->_admin_notices->add(
11130
					sprintf( $this->get_text( 'in-trial-mode' ), $this->_module_type ),
11131
					$this->get_text( 'oops' ) . '...',
11132
					'error'
11133
				);
11134
11135
				return false;
11136
			}
11137
11138
			if ( $this->_site->is_trial_utilized() ) {
11139
				// Trial was already utilized.
11140
				$this->_admin_notices->add(
11141
					$this->get_text( 'trial-utilized' ),
11142
					$this->get_text( 'oops' ) . '...',
11143
					'error'
11144
				);
11145
11146
				return false;
11147
			}
11148
11149
			if ( false !== $plan_name ) {
11150
				$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 11124 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...
11151
11152
				if ( false === $plan ) {
11153
					// Plan doesn't exist.
11154
					$this->_admin_notices->add(
11155
						sprintf( $this->get_text( 'trial-plan-x-not-exist' ), $plan_name ),
11156
						$this->get_text( 'oops' ) . '...',
11157
						'error'
11158
					);
11159
11160
					return false;
11161
				}
11162
11163
				if ( ! $plan->has_trial() ) {
11164
					// Plan doesn't exist.
11165
					$this->_admin_notices->add(
11166
						sprintf( $this->get_text( 'plan-x-no-trial' ), $plan_name ),
11167
						$this->get_text( 'oops' ) . '...',
11168
						'error'
11169
					);
11170
11171
					return false;
11172
				}
11173
			} else {
11174
				if ( ! $this->has_trial_plan() ) {
11175
					// None of the plans have a trial.
11176
					$this->_admin_notices->add(
11177
						sprintf( $this->get_text( 'no-trials' ), $this->_module_type ),
11178
						$this->get_text( 'oops' ) . '...',
11179
						'error'
11180
					);
11181
11182
					return false;
11183
				}
11184
11185
				$plans_with_trial = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
11186
11187
				$plan = $plans_with_trial[0];
11188
			}
11189
11190
			$api  = $this->get_api_site_scope();
11191
			$plan = $api->call( "plans/{$plan->id}/trials.json", 'post' );
11192
11193
			if ( ! $this->is_api_result_entity( $plan ) ) {
11194
				// Some API error while trying to start the trial.
11195
				$this->_admin_notices->add(
11196
					sprintf( $this->get_text( 'unexpected-api-error' ), $this->_module_type )
11197
					. ' ' . var_export( $plan, true ),
11198
					$this->get_text( 'oops' ) . '...',
11199
					'error'
11200
				);
11201
11202
				return false;
11203
			}
11204
11205
			// Sync license.
11206
			$this->_sync_license();
11207
11208
			return $this->is_trial();
11209
		}
11210
11211
		/**
11212
		 * Cancel site trial.
11213
		 *
11214
		 * @author Vova Feldman (@svovaf)
11215
		 * @since  1.0.9
11216
		 *
11217
		 * @uses   FS_Api
11218
		 */
11219
		private function _cancel_trial() {
11220
			$this->_logger->entrance();
11221
11222
			if ( ! $this->is_trial() ) {
11223
				$this->_admin_notices->add(
11224
					$this->get_text( 'trial-cancel-no-trial-message' ),
11225
					$this->get_text( 'oops' ) . '...',
11226
					'error'
11227
				);
11228
11229
				return;
11230
			}
11231
11232
			$api  = $this->get_api_site_scope();
11233
			$site = $api->call( 'trials.json', 'delete' );
11234
11235
			$trial_cancelled = false;
11236
11237
			if ( $this->is_api_result_entity( $site ) ) {
11238
				$prev_trial_ends = $this->_site->trial_ends;
11239
11240
				if ( $this->is_paid_trial() ) {
11241
					$this->_license->expiration   = $site->trial_ends;
11242
					$this->_license->is_cancelled = true;
11243
					$this->_update_site_license( $this->_license );
11244
					$this->_store_licenses();
11245
11246
					// Clear subscription reference.
11247
					$this->_sync_site_subscription( null );
11248
				}
11249
11250
				// Update site info.
11251
				$this->_site = new FS_Site( $site );
11252
				$this->_enrich_site_plan();
11253
11254
				$trial_cancelled = ( $prev_trial_ends != $site->trial_ends );
11255
			} 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...
11256
				// handle different error cases.
11257
11258
			}
11259
11260
			if ( $trial_cancelled ) {
11261
				// Remove previous sticky messages about upgrade or trial (if exist).
11262
				$this->_admin_notices->remove_sticky( array(
11263
					'trial_started',
11264
					'trial_promotion',
11265
					'plan_upgraded',
11266
				) );
11267
11268
				// Store site updates.
11269
				$this->_store_site();
11270
11271
				if ( ! $this->is_addon() ||
11272
				     ! $this->deactivate_premium_only_addon_without_license( true )
11273
				) {
11274
					$this->_admin_notices->add(
11275
						sprintf( $this->get_text( 'trial-cancel-message' ), $this->_storage->trial_plan->title )
11276
					);
11277
				}
11278
11279
				// Clear trial plan information.
11280
				unset( $this->_storage->trial_plan );
11281
			} else {
11282
				$this->_admin_notices->add(
11283
					$this->get_text( 'trial-cancel-failure-message' ),
11284
					$this->get_text( 'oops' ) . '...',
11285
					'error'
11286
				);
11287
			}
11288
		}
11289
11290
		/**
11291
		 * @author Vova Feldman (@svovaf)
11292
		 * @since  1.0.6
11293
		 *
11294
		 * @param bool|number $plugin_id
11295
		 *
11296
		 * @return bool
11297
		 */
11298
		private function _is_addon_id( $plugin_id ) {
11299
			return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id );
11300
		}
11301
11302
		/**
11303
		 * Check if user eligible to download premium version updates.
11304
		 *
11305
		 * @author Vova Feldman (@svovaf)
11306
		 * @since  1.0.6
11307
		 *
11308
		 * @return bool
11309
		 */
11310
		private function _can_download_premium() {
11311
			return $this->has_active_valid_license() ||
11312
			       ( $this->is_trial() && ! $this->get_trial_plan()->is_free() );
11313
		}
11314
11315
		/**
11316
		 *
11317
		 * @author Vova Feldman (@svovaf)
11318
		 * @since  1.0.6
11319
		 *
11320
		 * @param bool|number $addon_id
11321
		 * @param string      $type "json" or "zip"
11322
		 *
11323
		 * @return string
11324
		 */
11325
		private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) {
11326
11327
			$is_addon = $this->_is_addon_id( $addon_id );
11328
11329
			$is_premium = null;
11330
			if ( ! $is_addon ) {
11331
				$is_premium = $this->_can_download_premium();
11332
			} 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 11325 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...
11333
				$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 11325 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...
11334
			}
11335
11336
			// If add-on, then append add-on ID.
11337
			$endpoint = ( $is_addon ? "/addons/$addon_id" : '' ) .
11338
			            '/updates/latest.' . $type;
11339
11340
			// If add-on and not yet activated, try to fetch based on server licensing.
11341
			if ( is_bool( $is_premium ) ) {
11342
				$endpoint = add_query_arg( 'is_premium', json_encode( $is_premium ), $endpoint );
11343
			}
11344
11345
			if ( $this->has_secret_key() ) {
11346
				$endpoint = add_query_arg( 'type', 'all', $endpoint );
11347
			}
11348
11349
			return $endpoint;
11350
		}
11351
11352
		/**
11353
		 * @author Vova Feldman (@svovaf)
11354
		 * @since  1.0.4
11355
		 *
11356
		 * @param bool|number $addon_id
11357
		 * @param bool        $flush      Since 1.1.7.3
11358
		 * @param int         $expiration Since 1.2.2.7
11359
		 *
11360
		 * @return object|false Plugin latest tag info.
11361
		 */
11362
		function _fetch_latest_version( $addon_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
11363
			$this->_logger->entrance();
11364
11365
			/**
11366
			 * @since 1.1.7.3 Check for plugin updates from Freemius only if opted-in.
11367
			 * @since 1.1.7.4 Also check updates for add-ons.
11368
			 */
11369
			if ( ! $this->is_registered() &&
11370
			     ! $this->_is_addon_id( $addon_id )
11371
			) {
11372
				return false;
11373
			}
11374
11375
			$tag = $this->get_api_site_or_plugin_scope()->get(
11376
				$this->_get_latest_version_endpoint( $addon_id, 'json' ),
11377
				$flush,
11378
				$expiration
11379
			);
11380
11381
			$latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get';
11382
11383
			$this->_logger->departure( 'Latest version ' . $latest_version );
11384
11385
			return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false;
11386
		}
11387
11388
		#----------------------------------------------------------------------------------
11389
		#region Download Plugin
11390
		#----------------------------------------------------------------------------------
11391
11392
		/**
11393
		 * Download latest plugin version, based on plan.
11394
		 *
11395
		 * Not like _download_latest(), this will redirect the page
11396
		 * to secure download url to prevent dual download (from FS to WP server,
11397
		 * and then from WP server to the client / browser).
11398
		 *
11399
		 * @author Vova Feldman (@svovaf)
11400
		 * @since  1.0.9
11401
		 *
11402
		 * @param bool|number $plugin_id
11403
		 *
11404
		 * @uses   FS_Api
11405
		 * @uses   wp_redirect()
11406
		 */
11407
		private function download_latest_directly( $plugin_id = false ) {
11408
			$this->_logger->entrance();
11409
11410
			wp_redirect( $this->get_latest_download_api_url( $plugin_id ) );
11411
		}
11412
11413
		/**
11414
		 * Get latest plugin FS API download URL.
11415
		 *
11416
		 * @author Vova Feldman (@svovaf)
11417
		 * @since  1.0.9
11418
		 *
11419
		 * @param bool|number $plugin_id
11420
		 *
11421
		 * @return string
11422
		 */
11423
		private function get_latest_download_api_url( $plugin_id = false ) {
11424
			$this->_logger->entrance();
11425
11426
			return $this->get_api_site_scope()->get_signed_url(
11427
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
11428
			);
11429
		}
11430
11431
		/**
11432
		 * Get payment invoice URL.
11433
		 *
11434
		 * @author Vova Feldman (@svovaf)
11435
		 * @since  1.2.0
11436
		 *
11437
		 * @param bool|number $payment_id
11438
		 *
11439
		 * @return string
11440
		 */
11441
		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...
11442
			$this->_logger->entrance();
11443
11444
			return $this->get_api_user_scope()->get_signed_url(
11445
				"/payments/{$payment_id}/invoice.pdf"
11446
			);
11447
		}
11448
11449
		/**
11450
		 * Get latest plugin download link.
11451
		 *
11452
		 * @author Vova Feldman (@svovaf)
11453
		 * @since  1.0.9
11454
		 *
11455
		 * @param string      $label
11456
		 * @param bool|number $plugin_id
11457
		 *
11458
		 * @return string
11459
		 */
11460
		private function get_latest_download_link( $label, $plugin_id = false ) {
11461
			return sprintf(
11462
				'<a target="_blank" href="%s">%s</a>',
11463
				$this->_get_latest_download_local_url( $plugin_id ),
11464
				$label
11465
			);
11466
		}
11467
11468
		/**
11469
		 * Get latest plugin download local URL.
11470
		 *
11471
		 * @author Vova Feldman (@svovaf)
11472
		 * @since  1.0.9
11473
		 *
11474
		 * @param bool|number $plugin_id
11475
		 *
11476
		 * @return string
11477
		 */
11478
		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...
11479
			// Add timestamp to protect from caching.
11480
			$params = array( 'ts' => WP_FS__SCRIPT_START_TIME );
11481
11482
			if ( ! empty( $plugin_id ) ) {
11483
				$params['plugin_id'] = $plugin_id;
11484
			}
11485
11486
			return $this->get_account_url( 'download_latest', $params );
11487
		}
11488
11489
		#endregion Download Plugin ------------------------------------------------------------------
11490
11491
		/**
11492
		 * @author Vova Feldman (@svovaf)
11493
		 * @since  1.0.4
11494
		 *
11495
		 * @uses   FS_Api
11496
		 *
11497
		 * @param bool        $background Hints the method if it's a background updates check. If false, it means that
11498
		 *                                was initiated by the admin.
11499
		 * @param bool|number $plugin_id
11500
		 * @param bool        $flush      Since 1.1.7.3
11501
		 * @param int         $expiration Since 1.2.2.7
11502
		 */
11503
		private function check_updates(
11504
			$background = false,
11505
			$plugin_id = false,
11506
			$flush = true,
11507
			$expiration = WP_FS__TIME_24_HOURS_IN_SEC
11508
		) {
11509
			$this->_logger->entrance();
11510
11511
			// Check if there's a newer version for download.
11512
			$new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration );
11513
11514
			$update = null;
11515
			if ( is_object( $new_version ) ) {
11516
				$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...
11517
11518
				if ( ! $background ) {
11519
					$this->_admin_notices->add(
11520
						sprintf(
11521
							$this->get_text( 'version-x-released' ) . ' ' . $this->get_text( 'please-download-x' ),
11522
							$update->version,
11523
							sprintf(
11524
								'<a href="%s" target="_blank">%s</a>',
11525
								$this->get_account_url( 'download_latest' ),
11526
								sprintf( $this->get_text( 'latest-x-version' ), $this->_site->plan->title )
11527
							)
11528
						),
11529
						$this->get_text( 'new' ) . '!'
11530
					);
11531
				}
11532
			} else if ( false === $new_version && ! $background ) {
11533
				$this->_admin_notices->add(
11534
					$this->get_text( 'you-have-latest' ),
11535
					$this->get_text( 'you-are-good' )
11536
				);
11537
			}
11538
11539
			$this->_store_update( $update, true, $plugin_id );
11540
		}
11541
11542
		/**
11543
		 * @author Vova Feldman (@svovaf)
11544
		 * @since  1.0.4
11545
		 *
11546
		 * @param bool $flush Since 1.1.7.3 add 24 hour cache by default.
11547
		 *
11548
		 * @return FS_Plugin[]
11549
		 *
11550
		 * @uses   FS_Api
11551
		 */
11552
		private function sync_addons( $flush = false ) {
11553
			$this->_logger->entrance();
11554
11555
			$api = $this->get_api_site_or_plugin_scope();
11556
11557
			/**
11558
			 * @since 1.2.1
11559
			 *
11560
			 * If there's a cached version of the add-ons and not asking
11561
			 * for a flush, just use the currently stored add-ons.
11562
			 */
11563
			if ( ! $flush && $api->is_cached( '/addons.json?enriched=true' ) ) {
11564
				$addons = self::get_all_addons();
11565
11566
				return $addons[ $this->_plugin->id ];
11567
			}
11568
11569
			$result = $api->get( '/addons.json?enriched=true', $flush );
11570
11571
			$addons = array();
11572
			if ( $this->is_api_result_object( $result, 'plugins' ) &&
11573
			     is_array( $result->plugins )
11574
			) {
11575
				for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) {
11576
					$addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] );
11577
				}
11578
11579
				$this->_store_addons( $addons, true );
11580
			}
11581
11582
			return $addons;
11583
		}
11584
11585
		/**
11586
		 * Handle user email update.
11587
		 *
11588
		 * @author Vova Feldman (@svovaf)
11589
		 * @since  1.0.3
11590
		 * @uses   FS_Api
11591
		 *
11592
		 * @param string $new_email
11593
		 *
11594
		 * @return object
11595
		 */
11596
		private function update_email( $new_email ) {
11597
			$this->_logger->entrance();
11598
11599
11600
			$api  = $this->get_api_user_scope();
11601
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array(
11602
				'email'                   => $new_email,
11603
				'after_email_confirm_url' => $this->_get_admin_page_url(
11604
					'account',
11605
					array( 'fs_action' => 'sync_user' )
11606
				),
11607
			) );
11608
11609
			if ( ! isset( $user->error ) ) {
11610
				$this->_user->email       = $user->email;
11611
				$this->_user->is_verified = $user->is_verified;
11612
				$this->_store_user();
11613
			} 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...
11614
				// handle different error cases.
11615
11616
			}
11617
11618
			return $user;
11619
		}
11620
11621
		#----------------------------------------------------------------------------------
11622
		#region API Error Handling
11623
		#----------------------------------------------------------------------------------
11624
11625
		/**
11626
		 * @author Vova Feldman (@svovaf)
11627
		 * @since  1.1.1
11628
		 *
11629
		 * @param mixed $result
11630
		 *
11631
		 * @return bool Is API result contains an error.
11632
		 */
11633
		private function is_api_error( $result ) {
11634
			return FS_Api::is_api_error( $result );
11635
		}
11636
11637
		/**
11638
		 * Checks if given API result is a non-empty and not an error object.
11639
		 *
11640
		 * @author Vova Feldman (@svovaf)
11641
		 * @since  1.2.1.5
11642
		 *
11643
		 * @param mixed       $result
11644
		 * @param string|null $required_property Optional property we want to verify that is set.
11645
		 *
11646
		 * @return bool
11647
		 */
11648
		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...
11649
			return FS_Api::is_api_result_object( $result, $required_property );
11650
		}
11651
11652
		/**
11653
		 * Checks if given API result is a non-empty entity object with non-empty ID.
11654
		 *
11655
		 * @author Vova Feldman (@svovaf)
11656
		 * @since  1.2.1.5
11657
		 *
11658
		 * @param mixed $result
11659
		 *
11660
		 * @return bool
11661
		 */
11662
		private function is_api_result_entity( $result ) {
11663
			return FS_Api::is_api_result_entity( $result );
11664
		}
11665
11666
		#endregion
11667
11668
		/**
11669
		 * Make sure a given argument is an array of a specific type.
11670
		 *
11671
		 * @author Vova Feldman (@svovaf)
11672
		 * @since  1.2.1.5
11673
		 *
11674
		 * @param mixed  $array
11675
		 * @param string $class
11676
		 *
11677
		 * @return bool
11678
		 */
11679
		private function is_array_instanceof( $array, $class ) {
11680
			return ( is_array( $array ) && ( empty( $array ) || $array[0] instanceof $class ) );
11681
		}
11682
11683
		/**
11684
		 * Start install ownership change.
11685
		 *
11686
		 * @author Vova Feldman (@svovaf)
11687
		 * @since  1.1.1
11688
		 * @uses   FS_Api
11689
		 *
11690
		 * @param string $candidate_email
11691
		 *
11692
		 * @return bool Is ownership change successfully initiated.
11693
		 */
11694
		private function init_change_owner( $candidate_email ) {
11695
			$this->_logger->entrance();
11696
11697
			$api    = $this->get_api_site_scope();
11698
			$result = $api->call( "/users/{$this->_user->id}.json", 'put', array(
11699
				'email'             => $candidate_email,
11700
				'after_confirm_url' => $this->_get_admin_page_url(
11701
					'account',
11702
					array( 'fs_action' => 'change_owner' )
11703
				),
11704
			) );
11705
11706
			return ! $this->is_api_error( $result );
11707
		}
11708
11709
		/**
11710
		 * Handle install ownership change.
11711
		 *
11712
		 * @author Vova Feldman (@svovaf)
11713
		 * @since  1.1.1
11714
		 * @uses   FS_Api
11715
		 *
11716
		 * @return bool Was ownership change successfully complete.
11717
		 */
11718
		private function complete_change_owner() {
11719
			$this->_logger->entrance();
11720
11721
			$site_result = $this->get_api_site_scope( true )->get();
11722
			$site        = new FS_Site( $site_result );
11723
			$this->_site = $site;
11724
11725
			$user     = new FS_User();
11726
			$user->id = fs_request_get( 'user_id' );
11727
11728
			// Validate install's user and given user.
11729
			if ( $user->id != $this->_site->user_id ) {
11730
				return false;
11731
			}
11732
11733
			$user->public_key = fs_request_get( 'user_public_key' );
11734
			$user->secret_key = fs_request_get( 'user_secret_key' );
11735
11736
			// Fetch new user information.
11737
			$this->_user = $user;
11738
			$user_result = $this->get_api_user_scope( true )->get();
11739
			$user        = new FS_User( $user_result );
11740
			$this->_user = $user;
11741
11742
			$this->_set_account( $user, $site );
11743
11744
			return true;
11745
		}
11746
11747
		/**
11748
		 * Handle user name update.
11749
		 *
11750
		 * @author Vova Feldman (@svovaf)
11751
		 * @since  1.0.9
11752
		 * @uses   FS_Api
11753
		 *
11754
		 * @return object
11755
		 */
11756
		private function update_user_name() {
11757
			$this->_logger->entrance();
11758
			$name = fs_request_get( 'fs_user_name_' . $this->get_unique_affix(), '' );
11759
11760
			$api  = $this->get_api_user_scope();
11761
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array(
11762
				'name' => $name,
11763
			) );
11764
11765
			if ( ! isset( $user->error ) ) {
11766
				$this->_user->first = $user->first;
11767
				$this->_user->last  = $user->last;
11768
				$this->_store_user();
11769
			} 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...
11770
				// handle different error cases.
11771
11772
			}
11773
11774
			return $user;
11775
		}
11776
11777
		/**
11778
		 * Verify user email.
11779
		 *
11780
		 * @author Vova Feldman (@svovaf)
11781
		 * @since  1.0.3
11782
		 * @uses   FS_Api
11783
		 */
11784
		private function verify_email() {
11785
			$this->_handle_account_user_sync();
11786
11787
			if ( $this->_user->is_verified() ) {
11788
				return;
11789
			}
11790
11791
			$api    = $this->get_api_site_scope();
11792
			$result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array(
11793
				'after_email_confirm_url' => $this->_get_admin_page_url(
11794
					'account',
11795
					array( 'fs_action' => 'sync_user' )
11796
				)
11797
			) );
11798
11799
			if ( ! isset( $result->error ) ) {
11800
				$this->_admin_notices->add( sprintf(
11801
					$this->get_text( 'verification-email-sent-message' ),
11802
					sprintf( '<a href="mailto:%1s">%2s</a>', esc_url( $this->_user->email ), $this->_user->email )
11803
				) );
11804
			} 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...
11805
				// handle different error cases.
11806
11807
			}
11808
		}
11809
11810
		/**
11811
		 * @author Vova Feldman (@svovaf)
11812
		 * @since  1.1.2
11813
		 *
11814
		 * @param array $params
11815
		 *
11816
		 * @return string
11817
		 */
11818
		function get_activation_url( $params = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
11819
			if ( $this->is_addon() && $this->has_free_plan() ) {
11820
				/**
11821
				 * @author Vova Feldman (@svovaf)
11822
				 * @since  1.2.1.7 Add-on's activation is the parent's module activation.
11823
				 */
11824
				return $this->get_parent_instance()->get_activation_url( $params );
11825
			}
11826
11827
			return $this->apply_filters( 'connect_url', $this->_get_admin_page_url( '', $params ) );
11828
		}
11829
11830
		/**
11831
		 * @author Vova Feldman (@svovaf)
11832
		 * @since  1.2.1.5
11833
		 *
11834
		 * @param array $params
11835
		 *
11836
		 * @return string
11837
		 */
11838
		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...
11839
			$params['fs_action']       = 'reset_anonymous_mode';
11840
			$params['fs_unique_affix'] = $this->get_unique_affix();
11841
11842
			return $this->get_activation_url( $params );
11843
		}
11844
11845
		/**
11846
		 * Get the URL of the page that should be loaded after the user connect
11847
		 * or skip in the opt-in screen.
11848
		 *
11849
		 * @author Vova Feldman (@svovaf)
11850
		 * @since  1.1.3
11851
		 *
11852
		 * @param string $filter Filter name.
11853
		 * @param array  $params Since 1.2.2.7
11854
		 *
11855
		 * @return string
11856
		 */
11857
		function get_after_activation_url( $filter, $params = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
11858
			if ( $this->is_free_wp_org_theme() &&
11859
			     fs_request_has( 'pending_activation' )
11860
			) {
11861
				$first_time_path = '';
11862
			} else {
11863
				$first_time_path = $this->_menu->get_first_time_path();
11864
			}
11865
11866
			return add_query_arg( $params, $this->apply_filters(
11867
				$filter,
11868
				empty( $first_time_path ) ?
11869
					$this->_get_admin_page_url() :
11870
					$first_time_path
11871
			) );
11872
		}
11873
11874
		/**
11875
		 * Handle account page updates / edits / actions.
11876
		 *
11877
		 * @author Vova Feldman (@svovaf)
11878
		 * @since  1.0.2
11879
		 *
11880
		 */
11881
		private function _handle_account_edits() {
11882
			if ( ! $this->is_user_admin() ) {
11883
				return;
11884
			}
11885
11886
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
11887
			$action    = fs_get_action();
11888
11889
			switch ( $action ) {
11890
				case 'delete_account':
11891
					check_admin_referer( $action );
11892
11893
					if ( $plugin_id == $this->get_id() ) {
11894
						$this->delete_account_event();
11895
11896
						// Clear user and site.
11897
						$this->_site = null;
11898
						$this->_user = null;
11899
11900
						fs_redirect( $this->get_activation_url() );
11901
					} else {
11902
						if ( $this->is_addon_activated( $plugin_id ) ) {
11903
							$fs_addon = self::get_instance_by_id( $plugin_id );
11904
							$fs_addon->delete_account_event();
11905
11906
							fs_redirect( $this->_get_admin_page_url( 'account' ) );
11907
						}
11908
					}
11909
11910
					return;
11911
11912
				case 'downgrade_account':
11913
					check_admin_referer( $action );
11914
11915
					if ( $plugin_id == $this->get_id() ) {
11916
						$this->_downgrade_site();
11917
					} else if ( $this->is_addon_activated( $plugin_id ) ) {
11918
						$fs_addon = self::get_instance_by_id( $plugin_id );
11919
						$fs_addon->_downgrade_site();
11920
					}
11921
11922
					return;
11923
11924
				case 'activate_license':
11925
					check_admin_referer( $action );
11926
11927
					if ( $plugin_id == $this->get_id() ) {
11928
						$this->_activate_license();
11929
					} else {
11930
						if ( $this->is_addon_activated( $plugin_id ) ) {
11931
							$fs_addon = self::get_instance_by_id( $plugin_id );
11932
							$fs_addon->_activate_license();
11933
						}
11934
					}
11935
11936
					return;
11937
11938
				case 'deactivate_license':
11939
					check_admin_referer( $action );
11940
11941
					if ( $plugin_id == $this->get_id() ) {
11942
						$this->_deactivate_license();
11943
					} else {
11944
						if ( $this->is_addon_activated( $plugin_id ) ) {
11945
							$fs_addon = self::get_instance_by_id( $plugin_id );
11946
							$fs_addon->_deactivate_license();
11947
						}
11948
					}
11949
11950
					return;
11951
11952
				case 'check_updates':
11953
					check_admin_referer( $action );
11954
					$this->check_updates();
11955
11956
					return;
11957
11958
				case 'change_owner':
11959
					$state = fs_request_get( 'state', 'init' );
11960
					switch ( $state ) {
11961
						case 'init':
11962
							$candidate_email = fs_request_get( 'candidate_email', '' );
11963
11964
							if ( $this->init_change_owner( $candidate_email ) ) {
11965
								$this->_admin_notices->add( sprintf( $this->get_text( 'change-owner-request-sent-x' ), '<b>' . $this->_user->email . '</b>' ) );
11966
							}
11967
							break;
11968
						case 'owner_confirmed':
11969
							$candidate_email = fs_request_get( 'candidate_email', '' );
11970
11971
							$this->_admin_notices->add( sprintf( $this->get_text( 'change-owner-request_owner-confirmed' ), '<b>' . $candidate_email . '</b>' ) );
11972
							break;
11973
						case 'candidate_confirmed':
11974
							if ( $this->complete_change_owner() ) {
11975
								$this->_admin_notices->add_sticky(
11976
									sprintf( $this->get_text( 'change-owner-request_candidate-confirmed' ), '<b>' . $this->_user->email . '</b>' ),
11977
									'ownership_changed',
11978
									$this->get_text( 'congrats' ) . '!'
11979
								);
11980
							} 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...
11981
								// @todo Handle failed ownership change message.
11982
							}
11983
							break;
11984
					}
11985
11986
					return;
11987
11988
				case 'update_email':
11989
					check_admin_referer( 'update_email' );
11990
11991
					$new_email = fs_request_get( 'fs_email_' . $this->get_unique_affix(), '' );
11992
					$result    = $this->update_email( $new_email );
11993
11994
					if ( isset( $result->error ) ) {
11995
						switch ( $result->error->code ) {
11996
							case 'user_exist':
11997
								$this->_admin_notices->add(
11998
									$this->get_text( 'user-exist-message' ) . ' ' .
11999
									sprintf( $this->get_text( 'user-exist-message_ownership' ), $this->_module_type, '<b>' . $new_email . '</b>' ) .
12000
									sprintf(
12001
										'<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
12002
										$this->get_account_url( 'change_owner', array(
12003
											'state'           => 'init',
12004
											'candidate_email' => $new_email
12005
										) ),
12006
										$this->get_text( 'change-ownership' )
12007
									),
12008
									$this->get_text( 'oops' ) . '...',
12009
									'error'
12010
								);
12011
								break;
12012
						}
12013
					} else {
12014
						$this->_admin_notices->add( $this->get_text( 'email-updated-message' ) );
12015
					}
12016
12017
					return;
12018
12019
				case 'update_user_name':
12020
					check_admin_referer( 'update_user_name' );
12021
12022
					$result = $this->update_user_name();
12023
12024
					if ( isset( $result->error ) ) {
12025
						$this->_admin_notices->add(
12026
							$this->get_text( 'name-update-failed-message' ),
12027
							$this->get_text( 'oops' ) . '...',
12028
							'error'
12029
						);
12030
					} else {
12031
						$this->_admin_notices->add( $this->get_text( 'name-updated-message' ) );
12032
					}
12033
12034
					return;
12035
12036
				#region Actions that might be called from external links (e.g. email)
12037
12038
				case 'cancel_trial':
12039
					if ( $plugin_id == $this->get_id() ) {
12040
						$this->_cancel_trial();
12041
					} else {
12042
						if ( $this->is_addon_activated( $plugin_id ) ) {
12043
							$fs_addon = self::get_instance_by_id( $plugin_id );
12044
							$fs_addon->_cancel_trial();
12045
						}
12046
					}
12047
12048
					return;
12049
12050
				case 'verify_email':
12051
					$this->verify_email();
12052
12053
					return;
12054
12055
				case 'sync_user':
12056
					$this->_handle_account_user_sync();
12057
12058
					return;
12059
12060
				case $this->get_unique_affix() . '_sync_license':
12061
					$this->_sync_license();
12062
12063
					return;
12064
12065
				case 'download_latest':
12066
					$this->download_latest_directly( $plugin_id );
12067
12068
					return;
12069
12070
				#endregion
12071
			}
12072
12073
			if ( WP_FS__IS_POST_REQUEST ) {
12074
				$properties = array( 'site_secret_key', 'site_id', 'site_public_key' );
12075
				foreach ( $properties as $p ) {
12076
					if ( 'update_' . $p === $action ) {
12077
						check_admin_referer( $action );
12078
12079
						$this->_logger->log( $action );
12080
12081
						$site_property                      = substr( $p, strlen( 'site_' ) );
12082
						$site_property_value                = fs_request_get( 'fs_' . $p . '_' . $this->get_unique_affix(), '' );
12083
						$this->get_site()->{$site_property} = $site_property_value;
12084
12085
						// Store account after modification.
12086
						$this->_store_site();
12087
12088
						$this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value );
12089
12090
						$this->_admin_notices->add( sprintf(
12091
							$this->get_text( 'x-updated' ),
12092
							'<b>' . str_replace( '_', ' ', $p ) . '</b>' ) );
12093
12094
						return;
12095
					}
12096
				}
12097
			}
12098
		}
12099
12100
		/**
12101
		 * Account page resources load.
12102
		 *
12103
		 * @author Vova Feldman (@svovaf)
12104
		 * @since  1.0.6
12105
		 */
12106
		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...
12107
			$this->_logger->entrance();
12108
12109
			$this->_logger->info( var_export( $_REQUEST, true ) );
12110
12111
			fs_enqueue_local_style( 'fs_account', '/admin/account.css' );
12112
12113
			if ( $this->has_addons() ) {
12114
				wp_enqueue_script( 'plugin-install' );
12115
				add_thickbox();
12116
12117
				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...
12118
					$classes .= ' plugins-php';
12119
12120
					return $classes;
12121
				}
12122
12123
				add_filter( 'admin_body_class', 'fs_addons_body_class' );
12124
			}
12125
12126
			if ( $this->has_paid_plan() &&
12127
			     ! $this->has_any_license() &&
12128
			     ! $this->is_sync_executed() &&
12129
			     $this->is_tracking_allowed()
12130
			) {
12131
				/**
12132
				 * If no licenses found and no sync job was executed during the last 24 hours,
12133
				 * just execute the sync job right away (blocking execution).
12134
				 *
12135
				 * @since 1.1.7.3
12136
				 */
12137
				$this->run_manual_sync();
12138
			}
12139
12140
			$this->_handle_account_edits();
12141
12142
			$this->do_action( 'account_page_load_before_departure' );
12143
		}
12144
12145
		/**
12146
		 * Render account page.
12147
		 *
12148
		 * @author Vova Feldman (@svovaf)
12149
		 * @since  1.0.0
12150
		 */
12151
		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...
12152
			$this->_logger->entrance();
12153
12154
			$template = 'account.php';
12155
			if ( 'billing' === fs_request_get( 'tab' ) ) {
12156
				$template = 'billing.php';
12157
			}
12158
12159
			$vars = array( 'id' => $this->_module_id );
12160
12161
			/**
12162
			 * Added filter to the template to allow developers wrapping the template
12163
			 * in custom HTML (e.g. within a wizard/tabs).
12164
			 *
12165
			 * @author Vova Feldman (@svovaf)
12166
			 * @since  1.2.1.6
12167
			 */
12168
			echo $this->apply_filters( "templates/{$template}", fs_get_template( $template, $vars ) );
12169
		}
12170
12171
		/**
12172
		 * Render account connect page.
12173
		 *
12174
		 * @author Vova Feldman (@svovaf)
12175
		 * @since  1.0.7
12176
		 */
12177
		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...
12178
			$this->_logger->entrance();
12179
12180
			$vars = array( 'id' => $this->_module_id );
12181
12182
			/**
12183
			 * Added filter to the template to allow developers wrapping the template
12184
			 * in custom HTML (e.g. within a wizard/tabs).
12185
			 *
12186
			 * @author Vova Feldman (@svovaf)
12187
			 * @since  1.2.1.6
12188
			 */
12189
			echo $this->apply_filters( 'templates/connect.php', fs_get_template( 'connect.php', $vars ) );
12190
		}
12191
12192
		/**
12193
		 * Load required resources before add-ons page render.
12194
		 *
12195
		 * @author Vova Feldman (@svovaf)
12196
		 * @since  1.0.6
12197
		 */
12198
		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...
12199
			$this->_logger->entrance();
12200
12201
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
12202
12203
			wp_enqueue_script( 'plugin-install' );
12204
			add_thickbox();
12205
12206
			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 (L12117-12121) 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...
12207
				$classes .= ' plugins-php';
12208
12209
				return $classes;
12210
			}
12211
12212
			add_filter( 'admin_body_class', 'fs_addons_body_class' );
12213
12214
			if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) {
12215
				$this->_admin_notices->add(
12216
					sprintf( $this->get_text( 'addons-info-external-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
12217
					$this->get_text( 'heads-up' ),
12218
					'update-nag'
12219
				);
12220
			}
12221
		}
12222
12223
		/**
12224
		 * Render add-ons page.
12225
		 *
12226
		 * @author Vova Feldman (@svovaf)
12227
		 * @since  1.0.6
12228
		 */
12229
		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...
12230
			$this->_logger->entrance();
12231
12232
			$vars = array( 'id' => $this->_module_id );
12233
12234
			/**
12235
			 * Added filter to the template to allow developers wrapping the template
12236
			 * in custom HTML (e.g. within a wizard/tabs).
12237
			 *
12238
			 * @author Vova Feldman (@svovaf)
12239
			 * @since  1.2.1.6
12240
			 */
12241
			echo $this->apply_filters( 'templates/add-ons.php', fs_get_template( 'add-ons.php', $vars ) );
12242
		}
12243
12244
		/* Pricing & Upgrade
12245
		------------------------------------------------------------------------------------------------------------------*/
12246
		/**
12247
		 * Render pricing page.
12248
		 *
12249
		 * @author Vova Feldman (@svovaf)
12250
		 * @since  1.0.0
12251
		 */
12252
		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...
12253
			$this->_logger->entrance();
12254
12255
			$vars = array( 'id' => $this->_module_id );
12256
12257
			if ( 'true' === fs_request_get( 'checkout', false ) ) {
12258
				fs_require_once_template( 'checkout.php', $vars );
12259
			} else {
12260
				fs_require_once_template( 'pricing.php', $vars );
12261
			}
12262
		}
12263
12264
		#----------------------------------------------------------------------------------
12265
		#region Contact Us
12266
		#----------------------------------------------------------------------------------
12267
12268
		/**
12269
		 * Render contact-us page.
12270
		 *
12271
		 * @author Vova Feldman (@svovaf)
12272
		 * @since  1.0.3
12273
		 */
12274
		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...
12275
			$this->_logger->entrance();
12276
12277
			$vars = array( 'id' => $this->_module_id );
12278
			fs_require_once_template( 'contact.php', $vars );
12279
		}
12280
12281
		#endregion ------------------------------------------------------------------------
12282
12283
		/**
12284
		 * Hide all admin notices to prevent distractions.
12285
		 *
12286
		 * @author Vova Feldman (@svovaf)
12287
		 * @since  1.0.3
12288
		 *
12289
		 * @uses   remove_all_actions()
12290
		 */
12291
		private static function _hide_admin_notices() {
12292
			remove_all_actions( 'admin_notices' );
12293
			remove_all_actions( 'network_admin_notices' );
12294
			remove_all_actions( 'all_admin_notices' );
12295
			remove_all_actions( 'user_admin_notices' );
12296
		}
12297
12298
		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...
12299
			self::_hide_admin_notices();
12300
12301
			// Hide footer.
12302
			echo '<style>#wpfooter { display: none !important; }</style>';
12303
		}
12304
12305
		/**
12306
		 * Attach to admin_head hook to hide all admin notices.
12307
		 *
12308
		 * @author Vova Feldman (@svovaf)
12309
		 * @since  1.0.3
12310
		 */
12311
		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...
12312
			add_action( 'admin_head', 'Freemius::_clean_admin_content_section_hook' );
12313
		}
12314
12315
		/* CSS & JavaScript
12316
		------------------------------------------------------------------------------------------------------------------*/
12317
		/*		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...
12318
					$url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src );
12319
12320
					$this->_logger->entrance( 'script = ' . $url );
12321
12322
					wp_enqueue_script( $handle, $url );
12323
				}*/
12324
12325
		/* SDK
12326
		------------------------------------------------------------------------------------------------------------------*/
12327
		private $_user_api;
12328
12329
		/**
12330
		 *
12331
		 * @author Vova Feldman (@svovaf)
12332
		 * @since  1.0.2
12333
		 *
12334
		 * @param bool $flush
12335
		 *
12336
		 * @return FS_Api
12337
		 */
12338
		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...
12339
			if ( ! isset( $this->_user_api ) || $flush ) {
12340
				$this->_user_api = FS_Api::instance(
12341
					$this->_module_id,
12342
					'user',
12343
					$this->_user->id,
12344
					$this->_user->public_key,
12345
					! $this->is_live(),
12346
					$this->_user->secret_key
12347
				);
12348
			}
12349
12350
			return $this->_user_api;
12351
		}
12352
12353
		private $_site_api;
12354
12355
		/**
12356
		 *
12357
		 * @author Vova Feldman (@svovaf)
12358
		 * @since  1.0.2
12359
		 *
12360
		 * @param bool $flush
12361
		 *
12362
		 * @return FS_Api
12363
		 */
12364
		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...
12365
			if ( ! isset( $this->_site_api ) || $flush ) {
12366
				$this->_site_api = FS_Api::instance(
12367
					$this->_module_id,
12368
					'install',
12369
					$this->_site->id,
12370
					$this->_site->public_key,
12371
					! $this->is_live(),
12372
					$this->_site->secret_key
12373
				);
12374
			}
12375
12376
			return $this->_site_api;
12377
		}
12378
12379
		private $_plugin_api;
12380
12381
		/**
12382
		 * Get plugin public API scope.
12383
		 *
12384
		 * @author Vova Feldman (@svovaf)
12385
		 * @since  1.0.7
12386
		 *
12387
		 * @return FS_Api
12388
		 */
12389
		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...
12390
			if ( ! isset( $this->_plugin_api ) ) {
12391
				$this->_plugin_api = FS_Api::instance(
12392
					$this->_module_id,
12393
					'plugin',
12394
					$this->_plugin->id,
12395
					$this->_plugin->public_key,
12396
					! $this->is_live()
12397
				);
12398
			}
12399
12400
			return $this->_plugin_api;
12401
		}
12402
12403
		/**
12404
		 * Get site API scope object (fallback to public plugin scope when not registered).
12405
		 *
12406
		 * @author Vova Feldman (@svovaf)
12407
		 * @since  1.0.7
12408
		 *
12409
		 * @return FS_Api
12410
		 */
12411
		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...
12412
			return $this->is_registered() ?
12413
				$this->get_api_site_scope() :
12414
				$this->get_api_plugin_scope();
12415
		}
12416
12417
		/**
12418
		 * Show trial promotional notice (if any trial exist).
12419
		 *
12420
		 * @author Vova Feldman (@svovaf)
12421
		 * @since  1.0.9
12422
		 *
12423
		 * @param $plans
12424
		 */
12425
		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...
12426
			$this->_storage->has_trial_plan = FS_Plan_Manager::instance()->has_trial_plan( $plans );
12427
		}
12428
12429
		/**
12430
		 * During trial promotion the "upgrade" submenu item turns to
12431
		 * "start trial" to encourage the trial. Since we want to keep
12432
		 * the same menu item handler and there's no robust way to
12433
		 * add new arguments to the menu item link's querystring,
12434
		 * use JavaScript to find the menu item and update the href of
12435
		 * the link.
12436
		 *
12437
		 * @author Vova Feldman (@svovaf)
12438
		 * @since  1.2.1.5
12439
		 */
12440
		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...
12441
			$template_args = array( 'id' => $this->_module_id );
12442
			fs_require_template( 'add-trial-to-pricing.php', $template_args );
12443
		}
12444
12445
		/**
12446
		 * Check if module is currently in a trial promotion mode.
12447
		 *
12448
		 * @author Vova Feldman (@svovaf)
12449
		 * @since  1.2.2.7
12450
		 *
12451
		 * @return bool
12452
		 */
12453
		function is_in_trial_promotion() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12454
			return $this->_admin_notices->has_sticky( 'trial_promotion' );
12455
		}
12456
12457
		/**
12458
		 * Show trial promotional notice (if any trial exist).
12459
		 *
12460
		 * @author Vova Feldman (@svovaf)
12461
		 * @since  1.0.9
12462
		 *
12463
		 * @return bool If trial notice added.
12464
		 */
12465
		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...
12466
			if ( ! $this->is_user_admin() ) {
12467
				return false;
12468
			}
12469
12470
			if ( ! $this->is_user_in_admin() ) {
12471
				return false;
12472
			}
12473
12474
			// Check if trial message is already shown.
12475
			if ( $this->is_in_trial_promotion() ) {
12476
				add_action( 'admin_footer', array( &$this, '_fix_start_trial_menu_item_url' ) );
12477
12478
				$this->_menu->add_counter_to_menu_item( 1, 'fs-trial' );
12479
12480
				return false;
12481
			}
12482
12483
			if ( $this->is_premium() && ! WP_FS__DEV_MODE ) {
12484
				// Don't show trial if running the premium code, unless running in DEV mode.
12485
				return false;
12486
			}
12487
12488
			if ( ! $this->has_trial_plan() ) {
12489
				// No plans with trial.
12490
				return false;
12491
			}
12492
12493
			if ( ! $this->apply_filters( 'show_trial', true ) ) {
12494
				// Developer explicitly asked not to show the trial promo.
12495
				return false;
12496
			}
12497
12498
			if ( $this->is_registered() ) {
12499
				// Check if trial already utilized.
12500
				if ( $this->_site->is_trial_utilized() ) {
12501
					return false;
12502
				}
12503
12504
				if ( $this->is_paying_or_trial() ) {
12505
					// Don't show trial if paying or already in trial.
12506
					return false;
12507
				}
12508
			}
12509
12510
			if ( $this->is_activation_mode() || $this->is_pending_activation() ) {
12511
				// If not yet opted-in/skipped, or pending activation, don't show trial.
12512
				return false;
12513
			}
12514
12515
			$last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false );
12516
			$was_promotion_shown_before      = ( false !== $last_time_trial_promotion_shown );
12517
12518
			// Show promotion if never shown before and 24 hours after initial activation with FS.
12519
			if ( ! $was_promotion_shown_before &&
12520
			     $this->_storage->install_timestamp > ( time() - WP_FS__TIME_24_HOURS_IN_SEC )
12521
			) {
12522
				return false;
12523
			}
12524
12525
			// OR if promotion was shown before, try showing it every 30 days.
12526
			if ( $was_promotion_shown_before &&
12527
			     30 * WP_FS__TIME_24_HOURS_IN_SEC > time() - $last_time_trial_promotion_shown
12528
			) {
12529
				return false;
12530
			}
12531
12532
			$trial_period    = $this->_trial_days;
12533
			$require_payment = $this->_is_trial_require_payment;
12534
			$trial_url       = $this->get_trial_url();
12535
			$plans_string    = strtolower( $this->get_text( 'awesome' ) );
12536
12537
			if ( $this->is_registered() ) {
12538
				// If opted-in, override trial with up to date data from API.
12539
				$trial_plans       = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
12540
				$trial_plans_count = count( $trial_plans );
12541
12542
				if ( 0 === $trial_plans_count ) {
12543
					// If there's no plans with a trial just exit.
12544
					return false;
12545
				}
12546
12547
				/**
12548
				 * @var FS_Plugin_Plan $paid_plan
12549
				 */
12550
				$paid_plan       = $trial_plans[0];
12551
				$require_payment = $paid_plan->is_require_subscription;
12552
				$trial_period    = $paid_plan->trial_period;
12553
12554
				$total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 );
12555
12556
				if ( $total_paid_plans !== $trial_plans_count ) {
12557
					// Not all paid plans have a trial - generate a string of those that have it.
12558
					for ( $i = 0; $i < $trial_plans_count; $i ++ ) {
12559
						$plans_string .= sprintf(
12560
							' <a href="%s">%s</a>',
12561
							$trial_url,
12562
							$trial_plans[ $i ]->title
12563
						);
12564
12565
						if ( $i < $trial_plans_count - 2 ) {
12566
							$plans_string .= ', ';
12567
						} else if ( $i == $trial_plans_count - 2 ) {
12568
							$plans_string .= ' and ';
12569
						}
12570
					}
12571
				}
12572
			}
12573
12574
			$message = sprintf(
12575
				$this->get_text( 'hey' ) . '! ' . $this->get_text( 'trial-x-promotion-message' ),
12576
				sprintf( '<b>%s</b>', $this->get_plugin_name() ),
12577
				$plans_string,
12578
				$trial_period
12579
			);
12580
12581
			// "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...
12582
			$cc_string = $require_payment ?
12583
				sprintf( $this->get_text( 'no-commitment-for-x-days' ), $trial_period ) :
12584
				$this->get_text( 'no-cc-required' ) . '!';
12585
12586
12587
			// Start trial button.
12588
			$button = ' ' . sprintf(
12589
					'<a style="margin-left: 10px; vertical-align: super;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
12590
					$trial_url,
12591
					$this->get_text( 'start-free-trial' )
12592
				);
12593
12594
			$this->_admin_notices->add_sticky(
12595
				$this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ),
12596
				'trial_promotion',
12597
				'',
12598
				'promotion'
12599
			);
12600
12601
			$this->_storage->trial_promotion_shown = WP_FS__SCRIPT_START_TIME;
12602
12603
			return true;
12604
		}
12605
12606
		/**
12607
		 * @author Vova Feldman (@svovaf)
12608
		 * @since  1.2.1.5
12609
		 */
12610
		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...
12611
			if ( $this->has_paid_plan() && ! $this->is_paying() ) {
12612
				// Add basic CSS for admin-notices and menu-item colors.
12613
				fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
12614
			}
12615
		}
12616
12617
		/**
12618
		 * @author Leo Fajardo (leorw)
12619
		 * @since  1.2.2
12620
		 */
12621
		function _show_theme_activation_optin_dialog() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12622
			fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' );
12623
12624
			add_action( 'admin_footer-themes.php', array( &$this, '_add_fs_theme_activation_dialog' ) );
12625
		}
12626
12627
		/**
12628
		 * @author Leo Fajardo (leorw)
12629
		 * @since  1.2.2
12630
		 */
12631
		function _add_fs_theme_activation_dialog() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
12632
			$vars = array( 'id' => $this->_module_id );
12633
			fs_require_once_template( 'connect.php', $vars );
12634
		}
12635
12636
		/* Action Links
12637
		------------------------------------------------------------------------------------------------------------------*/
12638
		private $_action_links_hooked = false;
12639
		private $_action_links = array();
12640
12641
		/**
12642
		 * Hook to plugin action links filter.
12643
		 *
12644
		 * @author Vova Feldman (@svovaf)
12645
		 * @since  1.0.0
12646
		 */
12647
		private function hook_plugin_action_links() {
12648
			$this->_logger->entrance();
12649
12650
			$this->_action_links_hooked = true;
12651
12652
			$this->_logger->log( 'Adding action links hooks.' );
12653
12654
			// Add action link to settings page.
12655
			add_filter( 'plugin_action_links_' . $this->_plugin_basename, array(
12656
				&$this,
12657
				'_modify_plugin_action_links_hook'
12658
			), WP_FS__DEFAULT_PRIORITY, 2 );
12659
			add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array(
12660
				&$this,
12661
				'_modify_plugin_action_links_hook'
12662
			), WP_FS__DEFAULT_PRIORITY, 2 );
12663
		}
12664
12665
		/**
12666
		 * Add plugin action link.
12667
		 *
12668
		 * @author Vova Feldman (@svovaf)
12669
		 * @since  1.0.0
12670
		 *
12671
		 * @param      $label
12672
		 * @param      $url
12673
		 * @param bool $external
12674
		 * @param int  $priority
12675
		 * @param bool $key
12676
		 */
12677
		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...
12678
			$this->_logger->entrance();
12679
12680
			if ( ! isset( $this->_action_links[ $priority ] ) ) {
12681
				$this->_action_links[ $priority ] = array();
12682
			}
12683
12684
			if ( false === $key ) {
12685
				$key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) );
12686
			}
12687
12688
			$this->_action_links[ $priority ][] = array(
12689
				'label'    => $label,
12690
				'href'     => $url,
12691
				'key'      => $key,
12692
				'external' => $external
12693
			);
12694
		}
12695
12696
		/**
12697
		 * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection.
12698
		 *
12699
		 * @author Vova Feldman (@svovaf)
12700
		 * @since  1.0.0
12701
		 */
12702
		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...
12703
			$this->_logger->entrance();
12704
12705
			if ( $this->is_registered() ) {
12706
				if ( ! $this->is_paying() && $this->has_paid_plan() ) {
12707
					$this->add_plugin_action_link(
12708
						$this->get_text( 'upgrade' ),
12709
						$this->get_upgrade_url(),
12710
						false,
12711
						7,
12712
						'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...
12713
					);
12714
				}
12715
12716
				if ( $this->has_addons() ) {
12717
					$this->add_plugin_action_link(
12718
						$this->get_text( 'add-ons' ),
12719
						$this->_get_admin_page_url( 'addons' ),
12720
						false,
12721
						9,
12722
						'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...
12723
					);
12724
				}
12725
			}
12726
		}
12727
12728
		/**
12729
		 * Adds "Activate License" or "Change License" link to the main Plugins page link actions collection.
12730
		 *
12731
		 * @author Leo Fajardo (@leorw)
12732
		 * @since  1.1.9
12733
		 */
12734
		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...
12735
			$this->_logger->entrance();
12736
12737
			if ( $this->is_free_plan() && $this->is_addon() ) {
12738
				return;
12739
			}
12740
12741
			if ( ! self::is_ajax() ) {
12742
				// Inject license activation dialog UI and client side code.
12743
				add_action( 'admin_footer', array( &$this, '_add_license_activation_dialog_box' ) );
12744
			}
12745
12746
			$link_text = $this->get_text(
12747
				$this->is_free_plan() ?
12748
					'activate-license' :
12749
					'change-license'
12750
			);
12751
12752
			$this->add_plugin_action_link(
12753
				$link_text,
12754
				'#',
12755
				false,
12756
				11,
12757
				( 'activate-license ' . $this->get_unique_affix() )
0 ignored issues
show
Documentation introduced by
'activate-license ' . $this->get_unique_affix() is of type string, but the function expects a boolean.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
12758
			);
12759
		}
12760
12761
		/**
12762
		 * Adds "Opt in" or "Opt out" link to the main "Plugins" page link actions collection.
12763
		 *
12764
		 * @author Leo Fajardo (@leorw)
12765
		 * @since  1.2.1.5
12766
		 */
12767
		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...
12768
			if ( ! current_user_can( 'activate_plugins' ) ) {
12769
				return;
12770
			}
12771
12772
			$this->_logger->entrance();
12773
12774
			if ( fs_request_is_action_secure( $this->get_unique_affix() . '_reconnect' ) ) {
12775
				if ( ! $this->is_registered() && $this->is_anonymous() ) {
12776
					$this->connect_again();
12777
12778
					return;
12779
				}
12780
			}
12781
12782
			if ( ( $this->is_plugin() && ! self::is_plugins_page() ) ||
12783
			     ( $this->is_theme() && ! self::is_themes_page() )
12784
			) {
12785
				// Only show tracking links on the plugins and themes pages.
12786
				return;
12787
			}
12788
12789
			if ( ! $this->is_enable_anonymous() ) {
12790
				// Don't allow to opt-out if anonymous mode is disabled.
12791
				return;
12792
			}
12793
12794
			if ( ! $this->is_free_plan() ) {
12795
				// Don't allow to opt-out if running in paid plan.
12796
				return;
12797
			}
12798
12799
			if ( $this->add_ajax_action( 'stop_tracking', array( &$this, '_stop_tracking_callback' ) ) ) {
12800
				return;
12801
			}
12802
12803
			if ( $this->add_ajax_action( 'allow_tracking', array( &$this, '_allow_tracking_callback' ) ) ) {
12804
				return;
12805
			}
12806
12807
			$url = '#';
12808
12809
			if ( $this->is_registered() ) {
12810
				if ( $this->is_tracking_allowed() ) {
12811
					$link_text_id = 'opt-out';
12812
				} else {
12813
					$link_text_id = 'opt-in';
12814
				}
12815
12816
				add_action( 'admin_footer', array( &$this, '_add_optout_dialog' ) );
12817
			} else {
12818
				$link_text_id = 'opt-in';
12819
12820
				$params = ! $this->is_anonymous() ?
12821
					array() :
12822
					array(
12823
						'nonce'     => wp_create_nonce( $this->get_unique_affix() . '_reconnect' ),
12824
						'fs_action' => ( $this->get_unique_affix() . '_reconnect' ),
12825
					);
12826
12827
				$url = $this->get_activation_url( $params );
12828
			}
12829
12830
			if ( $this->is_plugin() && self::is_plugins_page() ) {
12831
				$this->add_plugin_action_link(
12832
					$this->get_text( $link_text_id ),
12833
					$url,
12834
					false,
12835
					13,
12836
					"opt-in-or-opt-out {$this->_slug}"
12837
				);
12838
			}
12839
		}
12840
12841
		/**
12842
		 * Get the URL of the page that should be loaded right after the plugin activation.
12843
		 *
12844
		 * @author Vova Feldman (@svovaf)
12845
		 * @since  1.1.7.4
12846
		 *
12847
		 * @return string
12848
		 */
12849
		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...
12850
			$url       = false;
12851
12852
			if ( ! $this->is_addon() || ! $this->has_free_plan() ) {
12853
				$first_time_path = $this->_menu->get_first_time_path();
12854
				$url             = $this->is_activation_mode() ?
12855
					$this->get_activation_url() :
12856
					( empty( $first_time_path ) ?
12857
						$this->_get_admin_page_url() :
12858
						$first_time_path );
12859
			} else {
12860
				$plugin_fs = false;
12861
12862
				if ( $this->is_parent_plugin_installed() ) {
12863
					$plugin_fs = self::get_parent_instance();
12864
				}
12865
12866
				if ( is_object( $plugin_fs ) ) {
12867
					if ( ! $plugin_fs->is_registered() ) {
12868
						// Forward to parent plugin connect when parent not registered.
12869
						$url = $plugin_fs->get_activation_url();
12870
					} else {
12871
						// Forward to account page.
12872
						$url = $plugin_fs->_get_admin_page_url( 'account' );
12873
					}
12874
				}
12875
			}
12876
12877
			return $url;
12878
		}
12879
12880
		/**
12881
		 * Forward page to activation page.
12882
		 *
12883
		 * @author Vova Feldman (@svovaf)
12884
		 * @since  1.0.3
12885
		 */
12886
		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...
12887
			$url = $this->get_after_plugin_activation_redirect_url();
12888
12889
			if ( is_string( $url ) ) {
12890
				fs_redirect( $url );
12891
			}
12892
		}
12893
12894
		/**
12895
		 * Modify plugin's page action links collection.
12896
		 *
12897
		 * @author Vova Feldman (@svovaf)
12898
		 * @since  1.0.0
12899
		 *
12900
		 * @param array $links
12901
		 * @param       $file
12902
		 *
12903
		 * @return array
12904
		 */
12905
		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...
12906
			$this->_logger->entrance();
12907
12908
			$passed_deactivate = false;
12909
			$deactivate_link   = '';
12910
			$before_deactivate = array();
12911
			$after_deactivate  = array();
12912
			foreach ( $links as $key => $link ) {
12913
				if ( 'deactivate' === $key ) {
12914
					$deactivate_link   = $link;
12915
					$passed_deactivate = true;
12916
					continue;
12917
				}
12918
12919
				if ( ! $passed_deactivate ) {
12920
					$before_deactivate[ $key ] = $link;
12921
				} else {
12922
					$after_deactivate[ $key ] = $link;
12923
				}
12924
			}
12925
12926
			ksort( $this->_action_links );
12927
12928
			foreach ( $this->_action_links as $new_links ) {
12929
				foreach ( $new_links as $link ) {
12930
					$before_deactivate[ $link['key'] ] = '<a href="' . $link['href'] . '"' . ( $link['external'] ? ' target="_blank"' : '' ) . '>' . $link['label'] . '</a>';
12931
				}
12932
			}
12933
12934
			if ( ! empty( $deactivate_link ) ) {
12935
				/**
12936
				 * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link.
12937
				 *
12938
				 * @since 1.2.1.6 Always show the deactivation feedback form since we added automatic free version deactivation upon premium code activation.
12939
				 */
12940
				$deactivate_link .= '<i class="fs-module-id" data-module-id="' . $this->_module_id . '"></i>';
12941
12942
				// Append deactivation link.
12943
				$before_deactivate['deactivate'] = $deactivate_link;
12944
			}
12945
12946
			return array_merge( $before_deactivate, $after_deactivate );
12947
		}
12948
12949
		/**
12950
		 * Adds admin message.
12951
		 *
12952
		 * @author Vova Feldman (@svovaf)
12953
		 * @since  1.0.4
12954
		 *
12955
		 * @param string $message
12956
		 * @param string $title
12957
		 * @param string $type
12958
		 */
12959
		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...
12960
			$this->_admin_notices->add( $message, $title, $type );
12961
		}
12962
12963
		/**
12964
		 * Adds sticky admin message.
12965
		 *
12966
		 * @author Vova Feldman (@svovaf)
12967
		 * @since  1.1.0
12968
		 *
12969
		 * @param string $message
12970
		 * @param string $id
12971
		 * @param string $title
12972
		 * @param string $type
12973
		 */
12974
		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...
12975
			$this->_admin_notices->add_sticky( $message, $id, $title, $type );
12976
		}
12977
12978
		/**
12979
		 * Helper function that returns the final steps for the upgrade completion.
12980
		 *
12981
		 * If the module is already running the premium code, returns an empty string.
12982
		 *
12983
		 * @author Vova Feldman (@svovaf)
12984
		 * @since  1.2.1
12985
		 *
12986
		 * @param string $plan_title
12987
		 *
12988
		 * @return string
12989
		 */
12990
		private function get_complete_upgrade_instructions( $plan_title = '' ) {
12991
			if ( ! $this->has_premium_version() || $this->is_premium() ) {
12992
				return '';
12993
			}
12994
12995
			if ( empty( $plan_title ) ) {
12996
				$plan_title = $this->_site->plan->title;
12997
			}
12998
12999
			// @since 1.2.1.5 The free version is auto deactivated.
13000
			$deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ?
13001
				( '<li>' . $this->get_text( 'deactivate-free-version' ) . '.</li>' ) :
13002
				'';
13003
13004
			return sprintf(
13005
				' %s: <ol><li>%s.</li>%s<li>%s (<a href="%s" target="_blank">%s</a>).</li></ol>',
13006
				$this->get_text( 'follow-steps-to-complete-upgrade' ),
13007
				$this->get_latest_download_link( sprintf(
13008
					$this->get_text( 'download-latest-x-version' ),
13009
					$plan_title
13010
				) ),
13011
				$deactivation_step,
13012
				$this->get_text( 'upload-and-activate' ),
13013
				'//bit.ly/upload-wp-' . $this->_module_type . 's',
13014
				$this->get_text( 'howto-upload-activate' )
13015
			);
13016
		}
13017
13018
		/**
13019
		 * @author Vova Feldman (@svovaf)
13020
		 * @since  1.2.1.7
13021
		 *
13022
		 * @param string $key
13023
		 *
13024
		 * @return string
13025
		 */
13026
		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...
13027
			return fs_text( $key, $this->_slug );
13028
		}
13029
13030
		#----------------------------------------------------------------------------------
13031
		#region Versioning
13032
		#----------------------------------------------------------------------------------
13033
13034
		/**
13035
		 * Check if Freemius in SDK upgrade mode.
13036
		 *
13037
		 * @author Vova Feldman (@svovaf)
13038
		 * @since  1.0.9
13039
		 *
13040
		 * @return bool
13041
		 */
13042
		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...
13043
			return isset( $this->_storage->sdk_upgrade_mode ) ?
13044
				$this->_storage->sdk_upgrade_mode :
13045
				false;
13046
		}
13047
13048
		/**
13049
		 * Turn SDK upgrade mode off.
13050
		 *
13051
		 * @author Vova Feldman (@svovaf)
13052
		 * @since  1.0.9
13053
		 */
13054
		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...
13055
			$this->_storage->sdk_upgrade_mode = false;
13056
		}
13057
13058
		/**
13059
		 * Check if plugin upgrade mode.
13060
		 *
13061
		 * @author Vova Feldman (@svovaf)
13062
		 * @since  1.0.9
13063
		 *
13064
		 * @return bool
13065
		 */
13066
		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...
13067
			return isset( $this->_storage->plugin_upgrade_mode ) ?
13068
				$this->_storage->plugin_upgrade_mode :
13069
				false;
13070
		}
13071
13072
		/**
13073
		 * Turn plugin upgrade mode off.
13074
		 *
13075
		 * @author Vova Feldman (@svovaf)
13076
		 * @since  1.0.9
13077
		 */
13078
		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...
13079
			$this->_storage->plugin_upgrade_mode = false;
13080
		}
13081
13082
		#endregion
13083
13084
		#----------------------------------------------------------------------------------
13085
		#region Permissions
13086
		#----------------------------------------------------------------------------------
13087
13088
		/**
13089
		 * Check if specific permission requested.
13090
		 *
13091
		 * @author Vova Feldman (@svovaf)
13092
		 * @since  1.1.6
13093
		 *
13094
		 * @param string $permission
13095
		 *
13096
		 * @return bool
13097
		 */
13098
		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...
13099
			return isset( $this->_permissions[ $permission ] ) && ( true === $this->_permissions[ $permission ] );
13100
		}
13101
13102
		#endregion
13103
13104
		#----------------------------------------------------------------------------------
13105
		#region Auto Activation
13106
		#----------------------------------------------------------------------------------
13107
13108
		/**
13109
		 * Hints the SDK if running an auto-installation.
13110
		 *
13111
		 * @var bool
13112
		 */
13113
		private $_isAutoInstall = false;
13114
13115
		/**
13116
		 * After upgrade callback to install and auto activate a plugin.
13117
		 * This code will only be executed on explicit request from the user,
13118
		 * following the practice Jetpack are using with their theme installations.
13119
		 *
13120
		 * @link   https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
13121
		 *
13122
		 * @author Vova Feldman (@svovaf)
13123
		 * @since  1.2.1.7
13124
		 */
13125
		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...
13126
			$this->_logger->entrance();
13127
13128
			$this->check_ajax_referer( 'install_premium_version' );
13129
13130
			if ( ! $this->is_registered() ) {
13131
				// Not registered.
13132
				self::shoot_ajax_failure( array(
13133
					'message' => $this->get_text( 'auto-install-error-not-opted-in' ),
13134
					'code'    => 'premium_installed',
13135
				) );
13136
			}
13137
13138
			$plugin_id = fs_request_get( 'module_id', $this->get_id() );
13139
13140
			if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) {
13141
				// Invalid ID.
13142
				self::shoot_ajax_failure( array(
13143
					'message' => $this->get_text( 'auto-install-error-invalid-id' ),
13144
					'code'    => 'invalid_module_id',
13145
				) );
13146
			}
13147
13148
			if ( $plugin_id == $this->get_id() ) {
13149
				if ( $this->is_premium() ) {
13150
					// Already using the premium code version.
13151
					self::shoot_ajax_failure( array(
13152
						'message' => $this->get_text( 'auto-install-error-premium-activated' ),
13153
						'code'    => 'premium_installed',
13154
					) );
13155
				}
13156
				if ( ! $this->can_use_premium_code() ) {
13157
					// Don't have access to the premium code.
13158
					self::shoot_ajax_failure( array(
13159
						'message' => $this->get_text( 'auto-install-error-invalid-license' ),
13160
						'code'    => 'invalid_license',
13161
					) );
13162
				}
13163
				if ( ! $this->has_release_on_freemius() ) {
13164
					// Plugin is a serviceware, no premium code version.
13165
					self::shoot_ajax_failure( array(
13166
						'message' => $this->get_text( 'auto-install-error-serviceware' ),
13167
						'code'    => 'premium_version_missing',
13168
					) );
13169
				}
13170
			} else {
13171
				$addon = $this->get_addon( $plugin_id );
13172
13173
				if ( ! is_object( $addon ) ) {
13174
					// Invalid add-on ID.
13175
					self::shoot_ajax_failure( array(
13176
						'message' => $this->get_text( 'auto-install-error-invalid-id' ),
13177
						'code'    => 'invalid_module_id',
13178
					) );
13179
				}
13180
13181
				if ( $this->is_addon_activated( $plugin_id, true ) ) {
13182
					// Premium add-on version is already activated.
13183
					self::shoot_ajax_failure( array(
13184
						'message' => $this->get_text( 'auto-install-error-premium-addon-activated' ),
13185
						'code'    => 'premium_installed',
13186
					) );
13187
				}
13188
			}
13189
13190
			$this->_isAutoInstall = true;
13191
13192
			// Try to install and activate.
13193
			$updater = new FS_Plugin_Updater( $this );
13194
			$result  = $updater->install_and_activate_plugin( $plugin_id );
13195
13196
			if ( is_array( $result ) && ! empty( $result['message'] ) ) {
13197
				self::shoot_ajax_failure( array(
13198
					'message' => $result['message'],
13199
					'code'    => $result['code'],
13200
				) );
13201
			}
13202
13203
			self::shoot_ajax_success( $result );
13204
		}
13205
13206
		/**
13207
		 * Displays module activation dialog box after a successful upgrade
13208
		 * where the user explicitly requested to auto download and install
13209
		 * the premium version.
13210
		 *
13211
		 * @author Vova Feldman (@svovaf)
13212
		 * @since  1.2.1.7
13213
		 */
13214
		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...
13215
			$this->_logger->entrance();
13216
13217
			if ( ! $this->is_registered() ) {
13218
				// Not registered.
13219
				return;
13220
			}
13221
13222
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
13223
13224
			if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) {
13225
				// Invalid module ID.
13226
				return;
13227
			}
13228
13229
			if ( $plugin_id == $this->get_id() ) {
13230
				if ( $this->is_premium() ) {
13231
					// Already using the premium code version.
13232
					return;
13233
				}
13234
				if ( ! $this->can_use_premium_code() ) {
13235
					// Don't have access to the premium code.
13236
					return;
13237
				}
13238
				if ( ! $this->has_release_on_freemius() ) {
13239
					// Plugin is a serviceware, no premium code version.
13240
					return;
13241
				}
13242
			} else {
13243
				$addon = $this->get_addon( $plugin_id );
13244
13245
				if ( ! is_object( $addon ) ) {
13246
					// Invalid add-on ID.
13247
					return;
13248
				}
13249
13250
				if ( $this->is_addon_activated( $plugin_id, true ) ) {
13251
					// Premium add-on version is already activated.
13252
					return;
13253
				}
13254
			}
13255
13256
			$vars = array(
13257
				'id'   => $plugin_id,
13258
				'slug' => $this->_slug,
13259
			);
13260
13261
			fs_require_template( 'auto-installation.php', $vars );
13262
		}
13263
13264
		#endregion
13265
13266
		#--------------------------------------------------------------------------------
13267
		#region Tabs Integration
13268
		#--------------------------------------------------------------------------------
13269
13270
		#region Module's Original Tabs
13271
13272
		/**
13273
		 * Inject a JavaScript logic to capture the theme tabs HTML.
13274
		 *
13275
		 * @author Vova Feldman (@svovaf)
13276
		 * @since  1.2.2.7
13277
		 */
13278
		function _tabs_capture() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13279
			$this->_logger->entrance();
13280
13281
			if ( ! $this->is_theme_settings_page() ||
13282
			     ! $this->is_matching_url( $this->main_menu_url() )
13283
			) {
13284
				return;
13285
			}
13286
13287
			$params = array(
13288
				'id' => $this->_module_id,
13289
			);
13290
13291
			fs_require_once_template( 'tabs-capture-js.php', $params );
13292
		}
13293
13294
		/**
13295
		 * Cache theme's tabs HTML for a week. The cache will also be set as expired
13296
		 * after version and type (free/premium) changes, in addition to the week period.
13297
		 *
13298
		 * @author Vova Feldman (@svovaf)
13299
		 * @since  1.2.2.7
13300
		 */
13301
		function _store_tabs_ajax_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13302
			$this->_logger->entrance();
13303
13304
			$this->check_ajax_referer( 'store_tabs' );
13305
13306
			// Init filesystem if not yet initiated.
13307
			WP_Filesystem();
13308
13309
			// Get POST body HTML data.
13310
			global $wp_filesystem;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
13311
			$tabs_html = $wp_filesystem->get_contents( "php://input" );
13312
13313
			if ( is_string( $tabs_html ) ) {
13314
				$tabs_html = trim( $tabs_html );
13315
			}
13316
13317
			if ( ! is_string( $tabs_html ) || empty( $tabs_html ) ) {
13318
				self::shoot_ajax_failure();
13319
			}
13320
13321
			$this->_cache->set( 'tabs', $tabs_html, 7 * WP_FS__TIME_24_HOURS_IN_SEC );
13322
13323
			self::shoot_ajax_success();
13324
		}
13325
13326
		/**
13327
		 * Cache theme's settings page custom styles. The cache will also be set as expired
13328
		 * after version and type (free/premium) changes, in addition to the week period.
13329
		 *
13330
		 * @author Vova Feldman (@svovaf)
13331
		 * @since  1.2.2.7
13332
		 */
13333
		function _store_tabs_styles() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13334
			$this->_logger->entrance();
13335
13336
			if ( ! $this->is_theme_settings_page() ||
13337
			     ! $this->is_matching_url( $this->main_menu_url() )
13338
			) {
13339
				return;
13340
			}
13341
13342
			$wp_styles = wp_styles();
13343
13344
			$theme_styles_url = get_template_directory_uri();
13345
13346
			$stylesheets = array();
13347
			foreach ( $wp_styles->queue as $handler ) {
13348
				if ( fs_starts_with( $handler, 'fs_' ) ) {
13349
					// Assume that stylesheets that their handler starts with "fs_" belong to the SDK.
13350
					continue;
13351
				}
13352
13353
				/**
13354
				 * @var _WP_Dependency $stylesheet
13355
				 */
13356
				$stylesheet = $wp_styles->registered[ $handler ];
13357
13358
				if ( fs_starts_with( $stylesheet->src, $theme_styles_url ) ) {
13359
					$stylesheets[] = $stylesheet->src;
13360
				}
13361
			}
13362
13363
			if ( ! empty( $stylesheets ) ) {
13364
				$this->_cache->set( 'tabs_stylesheets', $stylesheets, 7 * WP_FS__TIME_24_HOURS_IN_SEC );
13365
			}
13366
		}
13367
13368
		/**
13369
		 * Check if module's original settings page has any tabs.
13370
		 *
13371
		 * @author Vova Feldman (@svovaf)
13372
		 * @since  1.2.2.7
13373
		 *
13374
		 * @return bool
13375
		 */
13376
		private function has_tabs() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
13377
			return $this->_cache->has( 'tabs' );
13378
		}
13379
13380
		/**
13381
		 * Get module's settings page HTML content, starting
13382
		 * from the beginning of the <div class="wrap"> element,
13383
		 * until the tabs HTML (including).
13384
		 *
13385
		 * @author Vova Feldman (@svovaf)
13386
		 * @since  1.2.2.7
13387
		 *
13388
		 * @return string
13389
		 */
13390
		private function get_tabs_html() {
13391
			$this->_logger->entrance();
13392
13393
			return $this->_cache->get( 'tabs' );
13394
		}
13395
13396
		/**
13397
		 * Check if page should include tabs.
13398
		 *
13399
		 * @author Vova Feldman (@svovaf)
13400
		 * @since  1.2.2.7
13401
		 *
13402
		 * @return bool
13403
		 */
13404
		private function should_page_include_tabs()
13405
		{
13406
			if ( ! $this->has_settings_menu() ) {
13407
				// Don't add tabs if no settings at all.
13408
				return false;
13409
			}
13410
13411
			if ( ! $this->is_theme() ) {
13412
				// Only add tabs to themes for now.
13413
				return false;
13414
			}
13415
13416
			if ( ! $this->has_paid_plan() && ! $this->has_addons() ) {
13417
				// Only add tabs to monetizing themes.
13418
				return false;
13419
			}
13420
13421
			if ( ! $this->is_theme_settings_page() ) {
13422
				// Only add tabs if browsing one of the theme's setting pages.
13423
				return false;
13424
			}
13425
13426
			if ( $this->is_admin_page( 'pricing' ) && fs_request_get_bool( 'checkout' ) ) {
13427
				// Don't add tabs on checkout page, we want to reduce distractions
13428
				// as much as possible.
13429
				return false;
13430
			}
13431
13432
			return true;
13433
		}
13434
13435
		/**
13436
		 * Add the tabs HTML before the setting's page content and
13437
		 * enqueue any required stylesheets.
13438
		 *
13439
		 * @author Vova Feldman (@svovaf)
13440
		 * @since  1.2.2.7
13441
		 *
13442
		 * @return bool If tabs were included.
13443
		 */
13444
		function _add_tabs_before_content() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13445
			$this->_logger->entrance();
13446
13447
			if ( ! $this->should_page_include_tabs() ) {
13448
				return false;
13449
			}
13450
13451
			/**
13452
			 * Enqueue the original stylesheets that are included in the
13453
			 * theme settings page. That way, if the theme settings has
13454
			 * some custom _styled_ content above the tabs UI, this
13455
			 * will make sure that the styling is preserved.
13456
			 */
13457
			$stylesheets = $this->_cache->get( 'tabs_stylesheets', array() );
13458
			if ( is_array( $stylesheets ) ) {
13459
				for ( $i = 0, $len = count( $stylesheets ); $i < $len; $i ++ ) {
13460
					wp_enqueue_style( "fs_{$this->_module_id}_tabs_{$i}", $stylesheets[ $i ] );
13461
				}
13462
			}
13463
13464
			// Cut closing </div> tag.
13465
			echo substr( trim( $this->get_tabs_html() ), 0, - 6 );
13466
13467
			return true;
13468
		}
13469
13470
		/**
13471
		 * Add the tabs closing HTML after the setting's page content.
13472
		 *
13473
		 * @author Vova Feldman (@svovaf)
13474
		 * @since  1.2.2.7
13475
		 *
13476
		 * @return bool If tabs closing HTML was included.
13477
		 */
13478
		function _add_tabs_after_content() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13479
			$this->_logger->entrance();
13480
13481
			if ( ! $this->should_page_include_tabs() ) {
13482
				return false;
13483
			}
13484
13485
			echo '</div>';
13486
13487
			return true;
13488
		}
13489
13490
		#endregion
13491
13492
		/**
13493
		 * Add in-page JavaScript to inject the Freemius tabs into
13494
		 * the module's setting tabs section.
13495
		 *
13496
		 * @author Vova Feldman (@svovaf)
13497
		 * @since  1.2.2.7
13498
		 */
13499
		function _add_freemius_tabs() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13500
			$this->_logger->entrance();
13501
13502
			if ( ! $this->should_page_include_tabs() ) {
13503
				return;
13504
			}
13505
13506
			$params = array( 'id' => $this->_module_id );
13507
			fs_require_once_template( 'tabs.php', $params );
13508
		}
13509
13510
		#endregion
13511
13512
		#--------------------------------------------------------------------------------
13513
		#region Customizer Integration for Themes
13514
		#--------------------------------------------------------------------------------
13515
13516
		/**
13517
		 * @author Vova Feldman (@svovaf)
13518
		 * @since  1.2.2.7
13519
		 *
13520
		 * @param WP_Customize_Manager $customizer
13521
		 */
13522
		function _customizer_register($customizer) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13523
			$this->_logger->entrance();
13524
13525
			if ( $this->is_pricing_page_visible() ) {
13526
				require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-upsell-control.php';
13527
13528
				$customizer->add_section( 'freemius_upsell', array(
13529
					'title'    => '&#9733; ' . $this->get_text( 'view-paid-features' ),
13530
					'priority' => 1,
13531
				) );
13532
				$customizer->add_setting( 'freemius_upsell', array(
13533
					'sanitize_callback' => 'esc_html',
13534
				) );
13535
13536
				$customizer->add_control( new FS_Customizer_Upsell_Control( $customizer, 'freemius_upsell', array(
13537
					'fs'       => $this,
13538
					'section'  => 'freemius_upsell',
13539
					'priority' => 100,
13540
				) ) );
13541
			}
13542
13543
			if ( $this->is_page_visible( 'contact' ) || $this->is_page_visible( 'support' ) ) {
13544
				require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-support-section.php';
13545
13546
				// Main Documentation Link In Customizer Root.
13547
				$customizer->add_section( new FS_Customizer_Support_Section( $customizer, 'freemius_support', array(
13548
					'fs'       => $this,
13549
					'priority' => 1000,
13550
				) ) );
13551
			}
13552
		}
13553
13554
		#endregion
13555
13556
		/**
13557
		 * If the theme has a paid version, add some custom
13558
		 * styling to the theme's premium version (if exists)
13559
		 * to highlight that it's the premium version of the
13560
		 * same theme, making it easier for identification
13561
		 * after the user upgrades and upload it to the site.
13562
		 *
13563
		 * @author Vova Feldman (@svovaf)
13564
		 * @since  1.2.2.7
13565
		 */
13566
		function _style_premium_theme() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
13567
			$this->_logger->entrance();
13568
13569
			if ( ! self::is_themes_page() ) {
13570
				// Only include in the themes page.
13571
				return;
13572
			}
13573
13574
			if ( ! $this->has_paid_plan() ) {
13575
				// Only include if has any paid plans.
13576
				return;
13577
			}
13578
13579
			$params = null;
13580
			fs_require_once_template( '/js/jquery.content-change.php', $params );
13581
13582
			$params = array(
13583
				'slug' => $this->_slug,
13584
				'id'   => $this->_module_id,
13585
			);
13586
13587
			fs_require_template( '/js/style-premium-theme.php', $params );
13588
		}
13589
13590
		#----------------------------------------------------------------------------------
13591
		#region Marketing
13592
		#----------------------------------------------------------------------------------
13593
13594
		/**
13595
		 * Check if current user purchased any other plugins before.
13596
		 *
13597
		 * @author Vova Feldman (@svovaf)
13598
		 * @since  1.0.9
13599
		 *
13600
		 * @return bool
13601
		 */
13602
		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...
13603
			// TODO: Implement has_purchased_before() method.
13604
			throw new Exception( 'not implemented' );
13605
		}
13606
13607
		/**
13608
		 * Check if current user classified as an agency.
13609
		 *
13610
		 * @author Vova Feldman (@svovaf)
13611
		 * @since  1.0.9
13612
		 *
13613
		 * @return bool
13614
		 */
13615
		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...
13616
			// TODO: Implement is_agency() method.
13617
			throw new Exception( 'not implemented' );
13618
		}
13619
13620
		/**
13621
		 * Check if current user classified as a developer.
13622
		 *
13623
		 * @author Vova Feldman (@svovaf)
13624
		 * @since  1.0.9
13625
		 *
13626
		 * @return bool
13627
		 */
13628
		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...
13629
			// TODO: Implement is_developer() method.
13630
			throw new Exception( 'not implemented' );
13631
		}
13632
13633
		/**
13634
		 * Check if current user classified as a business.
13635
		 *
13636
		 * @author Vova Feldman (@svovaf)
13637
		 * @since  1.0.9
13638
		 *
13639
		 * @return bool
13640
		 */
13641
		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...
13642
			// TODO: Implement is_business() method.
13643
			throw new Exception( 'not implemented' );
13644
		}
13645
13646
		#endregion
13647
	}