Completed
Push — master ( e12db8...fe8184 )
by Ahmad
02:59
created

Freemius::get_email_sections()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 73
Code Lines 45

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 73
rs 8.683
nc 8
cc 4
eloc 45
nop 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

Loading history...
2
	/**
3
	 * @package     Freemius
4
	 * @copyright   Copyright (c) 2015, Freemius, Inc.
5
	 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
6
	 * @since       1.0.3
7
	 */
8
	if ( ! defined( 'ABSPATH' ) ) {
9
		exit;
10
	}
11
12
	// "final class" only supported since PHP 5.
13
	class Freemius extends Freemius_Abstract {
14
		/**
15
		 * SDK Version
16
		 *
17
		 * @var string
18
		 */
19
		public $version = '1.1.3';
20
21
		#region Plugin Info
22
23
		/**
24
		 * @since 1.0.1
25
		 *
26
		 * @var string
27
		 */
28
		private $_slug;
29
30
		/**
31
		 * @since 1.0.0
32
		 *
33
		 * @var string
34
		 */
35
		private $_plugin_basename;
36
		/**
37
		 * @since 1.0.0
38
		 *
39
		 * @var string
40
		 */
41
		private $_free_plugin_basename;
42
		/**
43
		 * @since 1.0.0
44
		 *
45
		 * @var string
46
		 */
47
		private $_plugin_dir_path;
48
		/**
49
		 * @since 1.0.0
50
		 *
51
		 * @var string
52
		 */
53
		private $_plugin_dir_name;
54
		/**
55
		 * @since 1.0.0
56
		 *
57
		 * @var string
58
		 */
59
		private $_plugin_main_file_path;
60
		/**
61
		 * @var string[]
62
		 */
63
		private $_plugin_data;
64
		/**
65
		 * @since 1.0.9
66
		 *
67
		 * @var string
68
		 */
69
		private $_plugin_name;
70
71
		#endregion Plugin Info
72
73
		/**
74
		 * @since 1.0.9
75
		 *
76
		 * @var bool If false, don't turn Freemius on.
77
		 */
78
		private $_is_on;
79
80
		/**
81
		 * @since 1.1.3
82
		 *
83
		 * @var bool If false, don't turn Freemius on.
84
		 */
85
		private $_is_anonymous;
86
87
		/**
88
		 * @since 1.0.9
89
		 * @var bool If false, issues with connectivity to Freemius API.
90
		 */
91
		private $_has_api_connection;
92
93
		/**
94
		 * @since 1.0.9
95
		 * @var bool Hints the SDK if plugin can support anonymous mode (if skip connect is visible).
96
		 */
97
		private $_enable_anonymous;
98
99
		/**
100
		 * @since 1.0.8
101
		 * @var bool Hints the SDK if the plugin has any paid plans.
102
		 */
103
		private $_has_paid_plans;
104
105
		/**
106
		 * @since 1.0.7
107
		 * @var bool Hints the SDK if the plugin is WordPress.org compliant.
108
		 */
109
		private $_is_org_compliant;
110
111
		/**
112
		 * @since 1.0.7
113
		 * @var bool Hints the SDK if the plugin is has add-ons.
114
		 */
115
		private $_has_addons;
116
117
		/**
118
		 * @var FS_Key_Value_Storage
119
		 */
120
		private $_storage;
121
122
		/**
123
		 * @since 1.0.0
124
		 *
125
		 * @var FS_Logger
126
		 */
127
		private $_logger;
128
		/**
129
		 * @since 1.0.4
130
		 *
131
		 * @var FS_Plugin
132
		 */
133
		private $_plugin = false;
134
		/**
135
		 * @since 1.0.4
136
		 *
137
		 * @var FS_Plugin
138
		 */
139
		private $_parent_plugin = false;
140
		/**
141
		 * @since 1.1.1
142
		 *
143
		 * @var Freemius
144
		 */
145
		private $_parent = false;
146
		/**
147
		 * @since 1.0.1
148
		 *
149
		 * @var FS_User
150
		 */
151
		private $_user = false;
152
		/**
153
		 * @since 1.0.1
154
		 *
155
		 * @var FS_Site
156
		 */
157
		private $_site = false;
158
		/**
159
		 * @since 1.0.1
160
		 *
161
		 * @var FS_Plugin_License
162
		 */
163
		private $_license;
164
		/**
165
		 * @since 1.0.2
166
		 *
167
		 * @var FS_Plugin_Plan[]
168
		 */
169
		private $_plans = false;
170
		/**
171
		 * @var FS_Plugin_License[]
172
		 * @since 1.0.5
173
		 */
174
		private $_licenses = false;
175
176
		/**
177
		 * @since 1.0.1
178
		 *
179
		 * @var FS_Admin_Menu_Manager
180
		 */
181
		private $_menu;
182
183
		/**
184
		 * @var FS_Admin_Notice_Manager
185
		 */
186
		private $_admin_notices;
187
188
		/**
189
		 * @var FS_Logger
190
		 * @since 1.0.0
191
		 */
192
		private static $_static_logger;
193
194
		/**
195
		 * @var FS_Option_Manager
196
		 * @since 1.0.2
197
		 */
198
		private static $_accounts;
199
200
		/**
201
		 * @var Freemius[]
202
		 */
203
		private static $_instances = array();
204
205
206
		/* Ctor
207
------------------------------------------------------------------------------------------------------------------*/
208
209
		private function __construct( $slug ) {
210
			$this->_slug = $slug;
211
212
			$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
213
214
			$this->_storage = FS_Key_Value_Storage::instance( 'plugin_data', $this->_slug );
215
216
			$this->_plugin_main_file_path = $this->_find_caller_plugin_file();
217
			$this->_plugin_dir_path       = plugin_dir_path( $this->_plugin_main_file_path );
218
			$this->_plugin_basename       = plugin_basename( $this->_plugin_main_file_path );
219
			$this->_free_plugin_basename  = str_replace( '-premium/', '/', $this->_plugin_basename );
220
221
			$base_name_split        = explode( '/', $this->_plugin_basename );
222
			$this->_plugin_dir_name = $base_name_split[0];
223
224
			if ( $this->_logger->is_on() ) {
225
				$this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path );
226
				$this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path );
227
				$this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename );
228
				$this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename );
229
				$this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name );
230
			}
231
232
			// Remember link between file to slug.
233
			$this->store_file_slug_map();
234
235
			// Store plugin's initial install timestamp.
236
			if ( ! isset( $this->_storage->install_timestamp ) ) {
237
				$this->_storage->install_timestamp = WP_FS__SCRIPT_START_TIME;
0 ignored issues
show
Documentation introduced by
The property install_timestamp 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...
238
			}
239
240
			$this->_plugin = FS_Plugin_Manager::instance( $this->_slug )->get();
241
242
			$this->_admin_notices = FS_Admin_Notice_Manager::instance(
243
				$slug,
244
				is_object( $this->_plugin ) ? $this->_plugin->title : ''
245
			);
246
247
			if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) ) {
248
				FS_Api::clear_cache();
249
			}
250
251
			$this->_register_hooks();
252
253
			$this->_load_account();
254
255
			$this->_version_updates_handler();
256
		}
257
258
		/**
259
		 * @author Vova Feldman (@svovaf)
260
		 * @since  1.0.9
261
		 */
262
		private function _version_updates_handler() {
263
			if ( ! isset( $this->_storage->sdk_version ) || $this->_storage->sdk_version != $this->version ) {
0 ignored issues
show
Documentation introduced by
The property sdk_version 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...
264
				// Freemius version upgrade mode.
265
				$this->_storage->sdk_last_version = $this->_storage->sdk_version;
0 ignored issues
show
Documentation introduced by
The property sdk_last_version 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...
Documentation introduced by
The property sdk_version 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...
266
				$this->_storage->sdk_version      = $this->version;
0 ignored issues
show
Documentation introduced by
The property sdk_version 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...
267
268
				if ( empty( $this->_storage->sdk_last_version ) ||
0 ignored issues
show
Documentation introduced by
The property sdk_last_version 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...
269
				     version_compare( $this->_storage->sdk_last_version, $this->version, '<' )
0 ignored issues
show
Documentation introduced by
The property sdk_last_version 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...
270
				) {
271
					$this->_storage->sdk_upgrade_mode   = true;
0 ignored issues
show
Documentation introduced by
The property sdk_upgrade_mode 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...
272
					$this->_storage->sdk_downgrade_mode = false;
0 ignored issues
show
Documentation introduced by
The property sdk_downgrade_mode 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...
273
				} else {
274
					$this->_storage->sdk_downgrade_mode = true;
0 ignored issues
show
Documentation introduced by
The property sdk_downgrade_mode 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...
275
					$this->_storage->sdk_upgrade_mode   = false;
0 ignored issues
show
Documentation introduced by
The property sdk_upgrade_mode 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...
276
277
				}
278
279
				$this->do_action( 'sdk_version_update' );
280
			}
281
282
			$plugin_version = $this->get_plugin_version();
283
			if ( ! isset( $this->_storage->plugin_version ) || $this->_storage->plugin_version != $plugin_version ) {
0 ignored issues
show
Documentation introduced by
The property plugin_version 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...
284
				// Plugin version upgrade mode.
285
				$this->_storage->plugin_last_version = $this->_storage->plugin_version;
0 ignored issues
show
Documentation introduced by
The property plugin_last_version 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...
Documentation introduced by
The property plugin_version 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...
286
				$this->_storage->plugin_version      = $plugin_version;
0 ignored issues
show
Documentation introduced by
The property plugin_version 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...
287
288
				if ( empty( $this->_storage->plugin_last_version ) ||
0 ignored issues
show
Documentation introduced by
The property plugin_last_version 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...
289
				     version_compare( $this->_storage->plugin_last_version, $plugin_version, '<' )
0 ignored issues
show
Documentation introduced by
The property plugin_last_version 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...
290
				) {
291
					$this->_storage->plugin_upgrade_mode   = true;
0 ignored issues
show
Documentation introduced by
The property plugin_upgrade_mode 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...
292
					$this->_storage->plugin_downgrade_mode = false;
0 ignored issues
show
Documentation introduced by
The property plugin_downgrade_mode 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...
293
				} else {
294
					$this->_storage->plugin_downgrade_mode = true;
0 ignored issues
show
Documentation introduced by
The property plugin_downgrade_mode 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...
295
					$this->_storage->plugin_upgrade_mode   = false;
0 ignored issues
show
Documentation introduced by
The property plugin_upgrade_mode 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...
296
				}
297
298
				$this->do_action( 'plugin_version_update' );
299
			}
300
		}
301
302
		/**
303
		 * @author Vova Feldman (@svovaf)
304
		 * @since  1.0.9
305
		 */
306
		private function _register_hooks() {
307
			if ( is_admin() ) {
308
				// Hook to plugin activation
309
				register_activation_hook( $this->_plugin_main_file_path, array(
310
					&$this,
311
					'_activate_plugin_event_hook'
312
				) );
313
314
				// Hook to plugin uninstall.
315
				register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) );
316
317
				if ( ! $this->is_ajax() ) {
318
					if ( ! $this->is_addon() ) {
319
						add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY );
320
						add_action( 'admin_menu', array( &$this, '_prepare_admin_menu' ), WP_FS__LOWEST_PRIORITY );
321
					}
322
				}
323
			}
324
325
			register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) );
326
327
			add_action( 'init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY );
328
329
			$this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) );
330
		}
331
332
		/**
333
		 * @author Vova Feldman (@svovaf)
334
		 * @since  1.0.9
335
		 */
336
		private function _register_account_hooks() {
337
			if ( is_admin() ) {
338
				if ( ! $this->is_ajax() ) {
339
					if ( $this->has_trial_plan() ) {
340
						$last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false );
341
						if ( ! $this->_site->is_trial_utilized() &&
342
						     (
343
							     // Show promotion if never shown it yet and 24 hours after initial activation.
344
							     ( false === $last_time_trial_promotion_shown && $this->_storage->activation_timestamp < ( time() - WP_FS__TIME_24_HOURS_IN_SEC ) ) ||
0 ignored issues
show
Documentation introduced by
The property activation_timestamp 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...
345
							     // Show promotion in every 30 days.
346
							     ( is_numeric( $last_time_trial_promotion_shown ) && 30 * WP_FS__TIME_24_HOURS_IN_SEC < time() - $last_time_trial_promotion_shown ) )
347
						) {
348
							$this->add_action( 'after_init_plugin_registered', array( &$this, '_add_trial_notice' ) );
349
						}
350
					}
351
				}
352
353
				// If user is paying or in trial and have the free version installed,
354
				// assume that the deactivation is for the upgrade process.
355
				if ( ! $this->is_paying_or_trial() || $this->is_premium() ) {
356
					add_action( 'wp_ajax_submit-uninstall-reason', array( &$this, '_submit_uninstall_reason_action' ) );
357
358
					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...
359
					if ( 'plugins.php' === $pagenow ) {
360
						add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) );
361
					}
362
				}
363
			}
364
		}
365
366
		/**
367
		 * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins
368
		 * page.
369
		 *
370
		 * @author Vova Feldman (@svovaf)
371
		 * @author Leo Fajardo (@leorw)
372
		 * @since  1.1.2
373
		 */
374
		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...
375
			fs_enqueue_local_style( 'fs_deactivation_feedback', '/admin/deactivation-feedback.css' );
376
377
			/* Check the type of user:
378
			 * 1. Long-term (long-term)
379
			 * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term).
380
			 * 3. Short-term (short-term)
381
			 */
382
			$is_long_term_user = true;
383
384
			// Check if the site is at least 2 days old.
385
			$time_installed = $this->_storage->install_timestamp;
0 ignored issues
show
Documentation introduced by
The property install_timestamp 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...
386
387
			// Difference in seconds.
388
			$date_diff = time() - $time_installed;
389
390
			// Convert seconds to days.
391
			$date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) );
392
393
			if ( $date_diff_days < 2 ) {
394
				$is_long_term_user = false;
395
			}
396
397
			$is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user );
398
399
			if ( $is_long_term_user ) {
400
				$user_type = 'long-term';
401
			} else {
402
				if ( ! $this->is_registered() && ! $this->is_anonymous() ) {
403
					$user_type = 'non-registered-and-non-anonymous-short-term';
404
				} else {
405
					$user_type = 'short-term';
406
				}
407
			}
408
409
			$uninstall_reasons = $this->_get_uninstall_reasons( $user_type );
410
411
			// Load the HTML template for the deactivation feedback dialog box.
412
			$vars = array(
413
				'reasons' => $uninstall_reasons,
414
				'slug'    => $this->_slug
415
			);
416
417
			fs_require_once_template( 'deactivation-feedback-modal.php', $vars );
418
		}
419
420
		/**
421
		 * @author Leo Fajardo (leorw)
422
		 * @since  1.1.2
423
		 *
424
		 * @param string $user_type
425
		 *
426
		 * @return array The uninstall reasons for the specified user type.
427
		 */
428
		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...
429
			$reason_found_better_plugin = array(
430
				'id'                => 2,
431
				'text'              => __fs( 'reason-found-a-better-plugin' ),
432
				'input_type'        => 'textfield',
433
				'input_placeholder' => __fs( 'placeholder-plugin-name' )
434
			);
435
436
			$reason_other = array(
437
				'id'                => 7,
438
				'text'              => __fs( 'reason-other' ),
439
				'input_type'        => 'textfield',
440
				'input_placeholder' => ''
441
			);
442
443
			$long_term_user_reasons = array(
444
				array(
445
					'id'                => 1,
446
					'text'              => __fs( 'reason-no-longer-needed' ),
447
					'input_type'        => '',
448
					'input_placeholder' => ''
449
				),
450
				$reason_found_better_plugin,
451
				array(
452
					'id'                => 3,
453
					'text'              => __fs( 'reason-needed-for-a-short-period' ),
454
					'input_type'        => '',
455
					'input_placeholder' => ''
456
				),
457
				array(
458
					'id'                => 4,
459
					'text'              => __fs( 'reason-broke-my-site' ),
460
					'input_type'        => '',
461
					'input_placeholder' => ''
462
				),
463
				array(
464
					'id'                => 5,
465
					'text'              => __fs( 'reason-suddenly-stopped-working' ),
466
					'input_type'        => '',
467
					'input_placeholder' => ''
468
				)
469
			);
470
471
			if ( $this->is_paying() ) {
472
				$long_term_user_reasons[] = array(
473
					'id'                => 6,
474
					'text'              => __fs( 'reason-cant-pay-anymore' ),
475
					'input_type'        => 'textfield',
476
					'input_placeholder' => __fs( 'placeholder-comfortable-price' )
477
				);
478
			}
479
480
			$long_term_user_reasons[] = $reason_other;
481
482
			$uninstall_reasons = array(
483
				'long-term'                                   => $long_term_user_reasons,
484
				'non-registered-and-non-anonymous-short-term' => array(
485
					array(
486
						'id'                => 8,
487
						'text'              => __fs( 'reason-didnt-work' ),
488
						'input_type'        => '',
489
						'input_placeholder' => ''
490
					),
491
					array(
492
						'id'                => 9,
493
						'text'              => __fs( 'reason-dont-like-to-share-my-information' ),
494
						'input_type'        => '',
495
						'input_placeholder' => ''
496
					),
497
					$reason_found_better_plugin,
498
					$reason_other
499
				),
500
				'short-term'                                  => array(
501
					array(
502
						'id'                => 10,
503
						'text'              => __fs( 'reason-couldnt-make-it-work' ),
504
						'input_type'        => '',
505
						'input_placeholder' => ''
506
					),
507
					$reason_found_better_plugin,
508
					array(
509
						'id'                => 11,
510
						'text'              => __fs( 'reason-great-but-need-specific-feature' ),
511
						'input_type'        => 'textarea',
512
						'input_placeholder' => __fs( 'placeholder-feature' )
513
					),
514
					array(
515
						'id'                => 12,
516
						'text'              => __fs( 'reason-not-working' ),
517
						'input_type'        => 'textarea',
518
						'input_placeholder' => __fs( 'placeholder-share-what-didnt-work' )
519
					),
520
					array(
521
						'id'                => 13,
522
						'text'              => __fs( 'reason-not-what-i-was-looking-for' ),
523
						'input_type'        => 'textarea',
524
						'input_placeholder' => __fs( 'placeholder-what-youve-been-looking-for' )
525
					),
526
					array(
527
						'id'                => 14,
528
						'text'              => __fs( 'reason-didnt-work-as-expected' ),
529
						'input_type'        => 'textarea',
530
						'input_placeholder' => __fs( 'placeholder-what-did-you-expect' )
531
					),
532
					$reason_other
533
				)
534
			);
535
536
			$uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons );
537
538
			return $uninstall_reasons[ $user_type ];
539
		}
540
541
		/**
542
		 * Called after the user has submitted his reason for deactivating the plugin.
543
		 *
544
		 * @author Leo Fajardo (@leorw)
545
		 * @since  1.1.2
546
		 */
547
		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...
548
			if ( ! isset( $_POST['reason_id'] ) ) {
549
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method _submit_uninstall_reason_action() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
550
			}
551
552
			$reason_info = isset( $_REQUEST['reason_info'] ) ? trim( stripslashes( $_REQUEST['reason_info'] ) ) : '';
553
554
			$reason = (object) array(
555
				'id'   => $_POST['reason_id'],
556
				'info' => substr( $reason_info, 0, 128 )
557
			);
558
559
			$this->_storage->store( 'uninstall_reason', $reason );
560
561
			// Print '1' for successful operation.
562
			echo 1;
563
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method _submit_uninstall_reason_action() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
564
		}
565
566
		/**
567
		 * Leverage backtrace to find caller plugin file path.
568
		 *
569
		 * @author Vova Feldman (@svovaf)
570
		 * @since  1.0.6
571
		 *
572
		 * @return string
573
		 */
574
		private function _find_caller_plugin_file() {
575
			$bt              = debug_backtrace();
576
			$abs_path_lenght = strlen( ABSPATH );
577
			$i               = 1;
578
			while (
579
				$i < count( $bt ) - 1 &&
580
				// substr is used to prevent cases where a freemius folder appears
581
				// in the path. For example, if WordPress is installed on:
582
				//  /var/www/html/some/path/freemius/path/wordpress/wp-content/...
583
				( false !== strpos( substr( fs_normalize_path( $bt[ $i ]['file'] ), $abs_path_lenght ), '/freemius/' ) ||
584
				  fs_normalize_path( dirname( dirname( $bt[ $i ]['file'] ) ) ) !== fs_normalize_path( WP_PLUGIN_DIR ) )
585
			) {
586
				$i ++;
587
			}
588
589
			return $bt[ $i ]['file'];
590
		}
591
592
		#region Instance ------------------------------------------------------------------
593
594
		/**
595
		 * Main singleton instance.
596
		 *
597
		 * @author Vova Feldman (@svovaf)
598
		 * @since  1.0.0
599
		 *
600
		 * @param $slug
601
		 *
602
		 * @return Freemius
603
		 */
604
		static function instance( $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...
605
			$slug = strtolower( $slug );
606
607
			if ( ! isset( self::$_instances[ $slug ] ) ) {
608
				if ( 0 === count( self::$_instances ) ) {
609
					self::_load_required_static();
610
				}
611
612
				self::$_instances[ $slug ] = new Freemius( $slug );
613
			}
614
615
			return self::$_instances[ $slug ];
616
		}
617
618
		/**
619
		 * @author Vova Feldman (@svovaf)
620
		 * @since  1.0.6
621
		 *
622
		 * @param string|number $slug_or_id
623
		 *
624
		 * @return bool
625
		 */
626
		private static function has_instance( $slug_or_id ) {
627
			return ! is_numeric( $slug_or_id ) ?
628
				isset( self::$_instances[ strtolower( $slug_or_id ) ] ) :
629
				( false !== self::get_instance_by_id( $slug_or_id ) );
630
		}
631
632
		/**
633
		 * @author Vova Feldman (@svovaf)
634
		 * @since  1.0.6
635
		 *
636
		 * @param $id
637
		 *
638
		 * @return false|Freemius
639
		 */
640
		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...
641
			foreach ( self::$_instances as $slug => $instance ) {
642
				if ( $id == $instance->get_id() ) {
643
					return $instance;
644
				}
645
			}
646
647
			return false;
648
		}
649
650
		/**
651
		 *
652
		 * @author Vova Feldman (@svovaf)
653
		 * @since  1.0.1
654
		 *
655
		 * @param $plugin_file
656
		 *
657
		 * @return false|Freemius
658
		 */
659
		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...
660
			$slug = self::find_slug_by_basename( $plugin_file );
661
662
			return ( false !== $slug ) ?
663
				self::instance( $slug ) :
664
				false;
665
		}
666
667
		/**
668
		 * @author Vova Feldman (@svovaf)
669
		 * @since  1.0.6
670
		 *
671
		 * @return false|Freemius
672
		 */
673
		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...
674
			return self::get_instance_by_id( $this->_plugin->parent_plugin_id );
675
		}
676
677
		/**
678
		 * @author Vova Feldman (@svovaf)
679
		 * @since  1.0.6
680
		 *
681
		 * @param $slug_or_id
682
		 *
683
		 * @return bool|Freemius
684
		 */
685
		function get_addon_instance( $slug_or_id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
686
			return ! is_numeric( $slug_or_id ) ?
687
				self::instance( strtolower( $slug_or_id ) ) :
688
				self::get_instance_by_id( $slug_or_id );
689
		}
690
691
		#endregion ------------------------------------------------------------------
692
693
		/**
694
		 * @author Vova Feldman (@svovaf)
695
		 * @since  1.0.6
696
		 *
697
		 * @return bool
698
		 */
699
		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...
700
			return self::has_instance( $this->_plugin->parent_plugin_id );
701
		}
702
703
		/**
704
		 * Check if add-on parent plugin in activation mode.
705
		 *
706
		 * @author Vova Feldman (@svovaf)
707
		 * @since  1.0.7
708
		 *
709
		 * @return bool
710
		 */
711
		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...
712
			$parent_fs = $this->get_parent_instance();
713
			if ( ! is_object( $parent_fs ) ) {
714
				return false;
715
			}
716
717
			return ( $parent_fs->is_activation_mode() );
718
		}
719
720
		/**
721
		 * Is plugin in activation mode.
722
		 *
723
		 * @author Vova Feldman (@svovaf)
724
		 * @since  1.0.7
725
		 *
726
		 * @return bool
727
		 */
728
		function is_activation_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...
729
			return (
730
				! $this->is_registered() &&
731
				( ! $this->enable_anonymous() ||
732
				  ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) )
733
			);
734
		}
735
736
		private static $_statics_loaded = false;
737
738
		/**
739
		 * Load static resources.
740
		 *
741
		 * @author Vova Feldman (@svovaf)
742
		 * @since  1.0.1
743
		 */
744
		private static function _load_required_static() {
745
			if ( self::$_statics_loaded ) {
746
				return;
747
			}
748
749
			self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
750
751
			self::$_static_logger->entrance();
752
753
			self::$_accounts = FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true );
754
755
			// Configure which Freemius powered plugins should be auto updated.
756
//			add_filter( 'auto_update_plugin', '_include_plugins_in_auto_update', 10, 2 );
757
758
			if ( WP_FS__DEV_MODE ) {
759
				add_action( 'admin_menu', array( 'Freemius', 'add_debug_page' ) );
760
			}
761
762
			self::$_statics_loaded = true;
763
		}
764
765
		#region Debugging ------------------------------------------------------------------
766
767
		/**
768
		 * @author Vova Feldman (@svovaf)
769
		 * @since  1.0.8
770
		 */
771
		static function add_debug_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...
772
			self::$_static_logger->entrance();
773
774
			$hook = add_object_page(
775
				__fs( 'freemius-debug' ),
776
				__fs( 'freemius-debug' ),
777
				'manage_options',
778
				'freemius',
779
				array( 'Freemius', '_debug_page_render' )
780
			);
781
782
			add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) );
783
		}
784
785
		/**
786
		 * @author Vova Feldman (@svovaf)
787
		 * @since  1.0.8
788
		 */
789
		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...
790
			if ( fs_request_is_action( 'delete_all_accounts' ) ) {
791
				check_admin_referer( 'delete_all_accounts' );
792
793
				self::$_accounts->clear( true );
794
795
				return;
796
			}
797
		}
798
799
		/**
800
		 * @author Vova Feldman (@svovaf)
801
		 * @since  1.0.8
802
		 */
803
		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...
804
			self::$_static_logger->entrance();
805
806
			$sites          = self::get_all_sites();
807
			$users          = self::get_all_users();
808
			$addons         = self::get_all_addons();
809
			$account_addons = self::get_all_account_addons();
810
811
//			$plans    = self::get_all_plans();
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...
812
//			$licenses = self::get_all_licenses();
813
814
			$vars = array(
815
				'sites'          => $sites,
816
				'users'          => $users,
817
				'addons'         => $addons,
818
				'account_addons' => $account_addons,
819
			);
820
			fs_require_once_template( 'debug.php', $vars );
821
		}
822
823
		#endregion ------------------------------------------------------------------
824
825
		#region Connectivity Issues ------------------------------------------------------------------
826
827
		/**
828
		 * Check if Freemius should be turned on for the current plugin install + version combination. The API query
829
		 * will be only invoked once per plugin version (cached locally).
830
		 *
831
		 * @author Vova Feldman (@svovaf)
832
		 * @since  1.0.9
833
		 *
834
		 * @return bool
835
		 */
836
		private function is_on() {
837
			self::$_static_logger->entrance();
838
839
			if ( isset( $this->_is_on ) ) {
840
				return $this->_is_on;
841
			}
842
843
			// If already installed then sure it's on :)
844
			if ( $this->is_registered() ) {
845
				$this->_is_on = true;
846
847
				return $this->_is_on;
848
			}
849
850
			$version = $this->get_plugin_version();
851
852
			if ( isset( $this->_storage->is_on ) ) {
853
				if ( $version == $this->_storage->is_on['version'] ) {
0 ignored issues
show
Documentation introduced by
The property is_on 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...
854
					$this->_is_on = $this->_storage->is_on['is_active'];
0 ignored issues
show
Documentation introduced by
The property is_on 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...
855
856
					return $this->_is_on;
857
				}
858
			}
859
860
			// Defaults to new install.
861
			$is_update = false;
862
			$is_update = $this->apply_filters( 'is_plugin_update', $is_update );
863
864
			/**
865
			 * Check anonymously if the SDK should be currently activated.
866
			 * The logic is based on whether the developer turned Freemius off,
867
			 * or set a limit to the number of activations. It's not related to
868
			 * any private information of the current WordPress instance.
869
			 *
870
			 * Note:
871
			 * Only the plugin's public key is being shared with the endpoint.
872
			 * NO private nor sensitive information is being shared.
873
			 */
874
			$result = $this->get_api_plugin_scope()->get(
875
				'is_active.json?is_update=' . json_encode( $is_update )
876
			);
877
878
			$is_active = ! isset( $result->error ) &&
879
			             isset( $result->is_active ) &&
880
			             is_bool( $result->is_active ) ?
881
				$result->is_active :
882
				false;
883
884
			$this->_storage->is_on = array(
0 ignored issues
show
Documentation introduced by
The property is_on 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...
885
				'is_active' => $is_active,
886
				'timestamp' => WP_FS__SCRIPT_START_TIME,
887
				'version'   => $version,
888
			);
889
890
			$this->_is_on = $is_active;
891
892
			return $this->_is_on;
893
		}
894
895
		/**
896
		 * Check if there's any connectivity issue to Freemius API.
897
		 *
898
		 * @author Vova Feldman (@svovaf)
899
		 * @since  1.0.9
900
		 *
901
		 * @return bool
902
		 */
903
		private function has_api_connectivity() {
904
			if ( isset( $this->_has_api_connection ) ) {
905
				return $this->_has_api_connection;
906
			}
907
908
			$version = $this->get_plugin_version();
909
910
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY &&
911
			     isset( $this->_storage->connectivity_test ) &&
912
			     true === $this->_storage->connectivity_test['is_connected']
0 ignored issues
show
Documentation introduced by
The property connectivity_test 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...
913
			) {
914
				unset( $this->_storage->connectivity_test );
915
			}
916
917
			if ( isset( $this->_storage->connectivity_test ) ) {
918
				if ( $version == $this->_storage->connectivity_test['version'] &&
0 ignored issues
show
Documentation introduced by
The property connectivity_test 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...
919
				     $_SERVER['HTTP_HOST'] == $this->_storage->connectivity_test['host'] &&
0 ignored issues
show
Documentation introduced by
The property connectivity_test 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...
920
				     $_SERVER['SERVER_ADDR'] == $this->_storage->connectivity_test['server_ip']
0 ignored issues
show
Documentation introduced by
The property connectivity_test 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...
921
				) {
922
					$this->_has_api_connection = $this->_storage->connectivity_test['is_connected'];
0 ignored issues
show
Documentation introduced by
The property connectivity_test 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...
923
924
					return $this->_has_api_connection;
925
				}
926
			}
927
928
			$is_connected = WP_FS__SIMULATE_NO_API_CONNECTIVITY ?
929
				false :
930
				$this->get_api_plugin_scope()->test( $this->get_anonymous_id() );
931
932
			if ( ! $is_connected ) {
933
				// 2nd try of connectivity.
934
				$pong = $this->get_api_plugin_scope()->ping( $this->get_anonymous_id() );
935
936
				if ( $this->get_api_plugin_scope()->is_valid_ping( $pong ) ) {
937
					$is_connected = true;
938
				} else {
939
					// Another API failure.
940
					$this->_add_connectivity_issue_message( $pong );
941
				}
942
			}
943
944
			$this->_storage->connectivity_test = array(
0 ignored issues
show
Documentation introduced by
The property connectivity_test 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...
945
				'is_connected' => $is_connected,
946
				'host'         => $_SERVER['HTTP_HOST'],
947
				'server_ip'    => $_SERVER['SERVER_ADDR'],
948
				'version'      => $version,
949
			);
950
951
			$this->_has_api_connection = $is_connected;
952
953
			return $this->_has_api_connection;
954
		}
955
956
		/**
957
		 * Anonymous and unique site identifier (Hash).
958
		 *
959
		 * @author Vova Feldman (@svovaf)
960
		 * @since  1.1.0
961
		 *
962
		 * @return string
963
		 */
964
		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...
965
			if ( ! self::$_accounts->has_option( 'unique_id' ) ) {
966
				$key = get_site_url();
967
968
				// If localhost, assign microtime instead of domain.
969
				if ( WP_FS__IS_LOCALHOST || false !== strpos( $key, 'localhost' ) ) {
970
					$key = microtime();
971
				}
972
973
				self::$_accounts->set_option( 'unique_id', md5( $key ), true );
974
			}
975
976
			return self::$_accounts->get_option( 'unique_id' );
977
		}
978
979
		/**
980
		 * Generate API connectivity issue message.
981
		 *
982
		 * @author Vova Feldman (@svovaf)
983
		 * @since  1.0.9
984
		 *
985
		 * @param mixed $api_result
986
		 */
987
		function _add_connectivity_issue_message( $api_result ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
988
			if ( $this->_enable_anonymous ) {
989
				// Don't add message if can run anonymously.
990
				return;
991
			}
992
993
			if ( ! function_exists( 'wp_nonce_url' ) ) {
994
				require_once( ABSPATH . 'wp-includes/functions.php' );
995
			}
996
997
			self::require_pluggable_essentials();
998
999
			$current_user = wp_get_current_user();
1000
//			$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...
1001
			$admin_email = $current_user->user_email;
1002
1003
			$message = false;
1004
			if ( is_object( $api_result ) &&
1005
			     isset( $api_result->error )
1006
			) {
1007
				switch ( $api_result->error->code ) {
1008
					case 'curl_missing':
1009
						$message = sprintf(
1010
							__fs( 'x-requires-access-to-api', 'freemius' ) . ' ' .
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1011
							__fs( 'curl-missing-message' ) . ' ' .
1012
							' %s',
1013
							'<b>' . $this->get_plugin_name() . '</b>',
1014
							sprintf(
1015
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
1016
								sprintf(
1017
									'<a class="fs-resolve" data-type="curl" href="#"><b>%s</b></a>%s',
1018
									__fs( 'curl-missing-no-clue-title' ),
1019
									' - ' . sprintf(
1020
										__fs( 'curl-missing-no-clue-desc' ),
1021
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1022
									)
1023
								),
1024
								sprintf(
1025
									'<b>%s</b> - %s',
1026
									__fs( 'sysadmin-title' ),
1027
									__fs( 'curl-missing-sysadmin-desc' )
1028
								),
1029
								sprintf(
1030
									'<a href="%s"><b>%s</b></a>%s',
1031
									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 ),
1032
									__fs( 'deactivate-plugin-title' ),
1033
									' - ' . __fs( 'deactivate-plugin-desc', 'freemius' )
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1034
								)
1035
							)
1036
						);
1037
						break;
1038
					case 'cloudflare_ddos_protection':
1039
						$message = sprintf(
1040
							__fs( 'x-requires-access-to-api', 'freemius' ) . ' ' .
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1041
							__fs( 'cloudflare-blocks-connection-message' ) . ' ' .
1042
							__fs( 'happy-to-resolve-issue-asap' ) .
1043
							' %s',
1044
							'<b>' . $this->get_plugin_name() . '</b>',
1045
							sprintf(
1046
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
1047
								sprintf(
1048
									'<a class="fs-resolve" data-type="cloudflare" href="#"><b>%s</b></a>%s',
1049
									__fs( 'fix-issue-title' ),
1050
									' - ' . sprintf(
1051
										__fs( 'fix-issue-desc' ),
1052
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1053
									)
1054
								),
1055
								sprintf(
1056
									'<a href="%s" target="_blank"><b>%s</b></a>%s',
1057
									sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
1058
									__fs( 'install-previous-title' ),
1059
									' - ' . __fs( 'install-previous-desc' )
1060
								),
1061
								sprintf(
1062
									'<a href="%s"><b>%s</b></a>%s',
1063
									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 ),
1064
									__fs( 'deactivate-plugin-title' ),
1065
									' - ' . __fs( 'deactivate-plugin-desc', 'freemius' )
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1066
								)
1067
							)
1068
						);
1069
						break;
1070
					case 'squid_cache_block':
1071
						$message = sprintf(
1072
							__fs( 'x-requires-access-to-api', 'freemius' ) . ' ' .
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1073
							__fs( 'squid-blocks-connection-message' ) .
1074
							' %s',
1075
							'<b>' . $this->get_plugin_name() . '</b>',
1076
							sprintf(
1077
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
1078
								sprintf(
1079
									'<a class="fs-resolve" data-type="squid" href="#"><b>%s</b></a>%s',
1080
									__fs( 'squid-no-clue-title' ),
1081
									' - ' . sprintf(
1082
										__fs( 'squid-no-clue-desc' ),
1083
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1084
									)
1085
								),
1086
								sprintf(
1087
									'<b>%s</b> - %s',
1088
									__fs( 'sysadmin-title' ),
1089
									sprintf(
1090
										__fs( 'squid-sysadmin-desc' ),
1091
										// We use a filter since the plugin might require additional API connectivity.
1092
										'<b>' . implode( ', ', $this->apply_filters( 'api_domains', array( 'api.freemius.com' ) ) ) . '</b>' )
1093
								),
1094
								sprintf(
1095
									'<a href="%s"><b>%s</b></a>%s',
1096
									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 ),
1097
									__fs( 'deactivate-plugin-title' ),
1098
									' - ' . __fs( 'deactivate-plugin-desc', 'freemius' )
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1099
								)
1100
							)
1101
						);
1102
						break;
1103
					default:
1104
						$message = __fs( 'connectivity-test-fails-message' );
1105
						break;
1106
				}
1107
			}
1108
1109
			if ( false === $message ) {
1110
				$message = sprintf(
1111
					__fs( 'x-requires-access-to-api', 'freemius' ) . ' ' .
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1112
					__fs( 'connectivity-test-fails-message' ) . ' ' .
1113
					__fs( 'happy-to-resolve-issue-asap' ) .
1114
					' %s',
1115
					'<b>' . $this->get_plugin_name() . '</b>',
1116
					sprintf(
1117
						'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
1118
						sprintf(
1119
							'<a class="fs-resolve" data-type="general" href="#"><b>%s</b></a>%s',
1120
							__fs( 'fix-issue-title' ),
1121
							' - ' . sprintf(
1122
								__fs( 'fix-issue-desc' ),
1123
								'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1124
							)
1125
						),
1126
						sprintf(
1127
							'<a href="%s" target="_blank"><b>%s</b></a>%s',
1128
							sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
1129
							__fs( 'install-previous-title' ),
1130
							' - ' . __fs( 'install-previous-desc' )
1131
						),
1132
						sprintf(
1133
							'<a href="%s"><b>%s</b></a>%s',
1134
							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 ),
1135
							__fs( 'deactivate-plugin-title' ),
1136
							' - ' . __fs( 'deactivate-plugin-desc', 'freemius' )
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1137
						)
1138
					)
1139
				);
1140
			}
1141
1142
			$this->_admin_notices->add_sticky(
1143
				$message,
1144
				'failed_connect_api',
1145
				__fs( 'oops' ) . '...',
1146
				'error'
1147
			);
1148
		}
1149
1150
		/**
1151
		 * Get collection of all active plugins.
1152
		 *
1153
		 * @author Vova Feldman (@svovaf)
1154
		 * @since  1.0.9
1155
		 *
1156
		 * @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...
1157
		 */
1158
		private function get_active_plugins() {
1159
			self::require_plugin_essentials();
1160
1161
			$active_plugin            = array();
1162
			$all_plugins              = get_plugins();
1163
			$active_plugins_basenames = get_option( 'active_plugins' );
1164
1165
			foreach ( $active_plugins_basenames as $plugin_basename ) {
1166
				$active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ];
1167
			}
1168
1169
			return $active_plugin;
1170
		}
1171
1172
		/**
1173
		 * Handle user request to resolve connectivity issue.
1174
		 * This method will send an email to Freemius API technical staff for resolution.
1175
		 * The email will contain server's info and installed plugins (might be caching issue).
1176
		 *
1177
		 * @author Vova Feldman (@svovaf)
1178
		 * @since  1.0.9
1179
		 */
1180
		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...
1181
			$this->_admin_notices->remove_sticky( 'failed_connect_api' );
1182
1183
			self::require_pluggable_essentials();
1184
1185
			$current_user = wp_get_current_user();
1186
			$admin_email  = $current_user->user_email;
1187
1188
			$ping = $this->get_api_plugin_scope()->ping();
1189
1190
			$error_type = fs_request_get( 'error_type', 'general' );
1191
1192
			switch ( $error_type ) {
1193
				case 'squid':
1194
					$title = 'Squid ACL Blocking Issue';
1195
					break;
1196
				case 'cloudflare':
1197
					$title = 'CloudFlare Blocking Issue';
1198
					break;
1199
				default:
1200
					$title = 'API Connectivity Issue';
1201
					break;
1202
			}
1203
1204
			$custom_email_sections = array();
1205
1206
			if ( 'squid' === $error_type ) {
1207
				// Override the 'Site' email section.
1208
				$custom_email_sections['site'] = array(
1209
					'rows' => array(
1210
						'hosting_company' => array( 'Hosting Company', fs_request_get( 'hosting_company' ) )
1211
					)
1212
				);
1213
			}
1214
1215
			// Add 'API Error' custom email section.
1216
			$custom_email_sections['api_error'] = array(
1217
				'title' => 'API Error',
1218
				'rows'  => array(
1219
					'ping' => array( is_string( $ping ) ? htmlentities( $ping ) : json_encode( $ping ) )
1220
				)
1221
			);
1222
1223
			// Send email with technical details to resolve CloudFlare's firewall unnecessary protection.
1224
			$this->send_email(
1225
				'[email protected]',                              // recipient
1226
				$title . ' [' . $this->get_plugin_name() . ']',  // subject
1227
				$custom_email_sections,
1228
				array( "Reply-To: $admin_email <$admin_email>" ) // headers
1229
			);
1230
1231
			$this->_admin_notices->add_sticky(
1232
				sprintf(
1233
					__fs( 'fix-request-sent-message' ),
1234
					'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1235
				),
1236
				'server_details_sent'
1237
			);
1238
1239
			// Action was taken, tell that API connectivity troubleshooting should be off now.
1240
1241
			echo "1";
1242
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method _email_about_firewall_issue() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1243
		}
1244
1245
		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...
1246
			$params = array();
1247
			fs_require_once_template( 'firewall-issues-js.php', $params );
1248
		}
1249
1250
		#endregion Connectivity Issues ------------------------------------------------------------------
1251
1252
		#region Email ------------------------------------------------------------------
1253
1254
		/**
1255
		 * Generates and sends an HTML email with customizable sections.
1256
		 *
1257
		 * @author Leo Fajardo (@leorw)
1258
		 * @since  1.1.2
1259
		 *
1260
		 * @param string $to_address
1261
		 * @param string $subject
1262
		 * @param array  $sections
1263
		 * @param array  $headers
1264
		 *
1265
		 * @return bool Whether the email contents were sent successfully.
1266
		 */
1267
		private function send_email(
1268
			$to_address,
1269
			$subject,
1270
			$sections = array(),
1271
			$headers = array()
1272
		) {
1273
			$default_sections = $this->get_email_sections();
1274
1275
			// Insert new sections or replace the default email sections.
1276
			if ( is_array( $sections ) && ! empty( $sections ) ) {
1277
				foreach ( $sections as $section_id => $custom_section ) {
1278
					if ( ! isset( $default_sections[ $section_id ] ) ) {
1279
						// If the section does not exist, add it.
1280
						$default_sections[ $section_id ] = $custom_section;
1281
					} else {
1282
						// If the section already exists, override it.
1283
						$current_section = $default_sections[ $section_id ];
1284
1285
						// Replace the current section's title if a custom section title exists.
1286
						if ( isset( $custom_section['title'] ) ) {
1287
							$current_section['title'] = $custom_section['title'];
1288
						}
1289
1290
						// Insert new rows under the current section or replace the default rows.
1291
						if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) {
1292
							foreach ( $custom_section['rows'] as $row_id => $row ) {
1293
								$current_section['rows'][ $row_id ] = $row;
1294
							}
1295
						}
1296
1297
						$default_sections[ $section_id ] = $current_section;
1298
					}
1299
				}
1300
			}
1301
1302
			$vars    = array( 'sections' => $default_sections );
1303
			$message = fs_get_template( 'email.php', $vars );
1304
1305
			// Set the type of email to HTML.
1306
			$headers[] = 'Content-type: text/html';
1307
1308
			$header_string = implode( "\r\n", $headers );
1309
1310
			return wp_mail(
1311
				$to_address,
1312
				$subject,
1313
				$message,
1314
				$header_string
1315
			);
1316
		}
1317
1318
		/**
1319
		 * Generates the data for the sections of the email content.
1320
		 *
1321
		 * @author Leo Fajardo (@leorw)
1322
		 * @since  1.1.2
1323
		 *
1324
		 * @return array
1325
		 */
1326
		private function get_email_sections() {
1327
			self::require_pluggable_essentials();
1328
1329
			// Retrieve the current user's information so that we can get the user's email, first name, and last name below.
1330
			$current_user = wp_get_current_user();
1331
1332
			// Retrieve the cURL version information so that we can get the version number below.
1333
			$curl_version_information = curl_version();
1334
1335
			$active_plugin = $this->get_active_plugins();
1336
1337
			// Generate the list of active plugins separated by new line. 
1338
			$active_plugin_string = '';
1339
			foreach ( $active_plugin as $plugin ) {
1340
				$active_plugin_string .= sprintf(
1341
					'<a href="%s">%s</a> [v%s]<br>',
1342
					$plugin['PluginURI'],
1343
					$plugin['Name'],
1344
					$plugin['Version']
1345
				);
1346
			}
1347
1348
			// Generate the default email sections.
1349
			$sections = array(
1350
				'sdk'     => array(
1351
					'title' => 'SDK',
1352
					'rows'  => array(
1353
						'fs_version'   => array( 'FS Version', $this->version ),
1354
						'curl_version' => array( 'cURL Version', $curl_version_information['version'] )
1355
					)
1356
				),
1357
				'plugin'  => array(
1358
					'title' => 'Plugin',
1359
					'rows'  => array(
1360
						'name'    => array( 'Name', $this->get_plugin_name() ),
1361
						'version' => array( 'Version', $this->get_plugin_version() )
1362
					)
1363
				),
1364
				'site'    => array(
1365
					'title' => 'Site',
1366
					'rows'  => array(
1367
						'address'     => array( 'Address', site_url() ),
1368
						'host'        => array(
1369
							'HTTP_HOST',
1370
							( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' )
1371
						),
1372
						'server_addr' => array(
1373
							'SERVER_ADDR',
1374
							( ! empty( $_SERVER['SERVER_ADDR'] ) ? '<a href="http://www.projecthoneypot.org/ip_' . $_SERVER['SERVER_ADDR'] . '">' . $_SERVER['SERVER_ADDR'] . '</a>' : '' )
1375
						)
1376
					)
1377
				),
1378
				'user'    => array(
1379
					'title' => 'User',
1380
					'rows'  => array(
1381
						'email' => array( 'Email', $current_user->user_email ),
1382
						'first' => array( 'First', $current_user->user_firstname ),
1383
						'last'  => array( 'Last', $current_user->user_lastname )
1384
					)
1385
				),
1386
				'plugins' => array(
1387
					'title' => 'Plugins',
1388
					'rows'  => array(
1389
						'active_plugins' => array( 'Active Plugins', $active_plugin_string )
1390
					)
1391
				),
1392
			);
1393
1394
			// Allow the sections to be modified by other code.
1395
			$sections = $this->apply_filters( 'email_template_sections', $sections );
1396
1397
			return $sections;
1398
		}
1399
1400
		#endregion Email ------------------------------------------------------------------
1401
1402
		#region Initialization ------------------------------------------------------------------
1403
1404
		/**
1405
		 * Init plugin's Freemius instance.
1406
		 *
1407
		 * @author Vova Feldman (@svovaf)
1408
		 * @since  1.0.1
1409
		 *
1410
		 * @param number $id
1411
		 * @param string $public_key
1412
		 * @param bool   $is_live
1413
		 * @param bool   $is_premium
1414
		 */
1415
		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...
1416
			$this->_logger->entrance();
1417
1418
			$this->dynamic_init( array(
1419
				'id'         => $id,
1420
				'public_key' => $public_key,
1421
				'is_live'    => $is_live,
1422
				'is_premium' => $is_premium,
1423
			) );
1424
		}
1425
1426
		private function _get_option( &$options, $key, $default = false ) {
1427
			return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
1428
		}
1429
1430
		private function _get_bool_option( &$options, $key, $default = false ) {
1431
			return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
1432
		}
1433
1434
		private function _get_numeric_option( &$options, $key, $default = false ) {
1435
			return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default;
1436
		}
1437
1438
		/**
1439
		 * Dynamic initiator, originally created to support initiation
1440
		 * with parent_id for add-ons.
1441
		 *
1442
		 * @author Vova Feldman (@svovaf)
1443
		 * @since  1.0.6
1444
		 *
1445
		 * @param array $plugin_info
1446
		 *
1447
		 * @throws Freemius_Exception
1448
		 */
1449
		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...
1450
			$this->_logger->entrance();
1451
1452
			$id          = $this->_get_numeric_option( $plugin_info, 'id', false );
1453
			$public_key  = $this->_get_option( $plugin_info, 'public_key', false );
1454
			$secret_key  = $this->_get_option( $plugin_info, 'secret_key', null );
1455
			$parent_id   = $this->_get_numeric_option( $plugin_info, 'parent_id', null );
1456
			$parent_name = $this->_get_option( $plugin_info, 'parent_name', null );
1457
1458
			if ( isset( $plugin_info['parent'] ) ) {
1459
				$parent_id = $this->_get_numeric_option( $plugin_info['parent'], 'id', null );
1460
//				$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...
1461
//				$parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null );
1462
				$parent_name = $this->_get_option( $plugin_info['parent'], 'name', null );
1463
			}
1464
1465
			if ( false === $id ) {
1466
				throw new Freemius_Exception( 'Plugin id parameter is not set.' );
1467
			}
1468
			if ( false === $public_key ) {
1469
				throw new Freemius_Exception( 'Plugin public_key parameter is not set.' );
1470
			}
1471
1472
			$plugin = ( $this->_plugin instanceof FS_Plugin ) ?
1473
				$this->_plugin :
1474
				new FS_Plugin();
1475
1476
			$plugin->update( array(
1477
				'id'               => $id,
1478
				'public_key'       => $public_key,
1479
				'slug'             => $this->_slug,
1480
				'parent_plugin_id' => $parent_id,
1481
				'version'          => $this->get_plugin_version(),
1482
				'title'            => $this->get_plugin_name(),
1483
				'file'             => $this->_free_plugin_basename,
1484
				'is_premium'       => $this->_get_bool_option( $plugin_info, 'is_premium', true ),
1485
				'is_live'          => $this->_get_bool_option( $plugin_info, 'is_live', true ),
1486
//				'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...
1487
			) );
1488
1489
			if ( $plugin->is_updated() ) {
1490
				// Update plugin details.
1491
				$this->_plugin = FS_Plugin_Manager::instance( $this->_slug )->store( $plugin );
1492
			}
1493
			$this->_plugin->secret_key = $secret_key;
1494
1495
			if ( ! isset( $plugin_info['menu'] ) ) {
1496
				// Back compatibility to 1.1.2
1497
				$plugin_info['menu'] = array(
1498
					'slug' => isset( $plugin_info['menu_slug'] ) ?
1499
						$plugin_info['menu_slug'] :
1500
						$this->_slug
1501
				);
1502
			}
1503
1504
			$this->_menu = FS_Admin_Menu_Manager::instance( $this->_slug );
1505
			$this->_menu->init( $plugin_info['menu'], $this->is_addon() );
1506
1507
			$this->_has_addons       = $this->_get_bool_option( $plugin_info, 'has_addons', false );
1508
			$this->_has_paid_plans   = $this->_get_bool_option( $plugin_info, 'has_paid_plans', true );
1509
			$this->_is_org_compliant = $this->_get_bool_option( $plugin_info, 'is_org_compliant', true );
1510
			$this->_enable_anonymous = $this->_get_bool_option( $plugin_info, 'enable_anonymous', true );
1511
1512
			if ( ! $this->is_registered() ) {
1513
				if ( ! $this->has_api_connectivity() ) {
1514
					if ( is_admin() && $this->_admin_notices->has_sticky( 'failed_connect_api' ) ) {
1515
						if ( ! $this->_enable_anonymous ) {
1516
							// If anonymous mode is disabled, add firewall admin-notice message.
1517
							add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) );
1518
1519
							add_action( "wp_ajax_{$this->_slug}_resolve_firewall_issues", array(
1520
								&$this,
1521
								'_email_about_firewall_issue'
1522
							) );
1523
						}
1524
					}
1525
1526
					// Turn Freemius off.
1527
					$this->_is_on = false;
1528
1529
					return;
1530
				}
1531
1532
				// Check if Freemius is on for the current plugin.
1533
				// This MUST be executed after all the plugin variables has been loaded.
1534
				if ( ! $this->is_on() ) {
1535
					return;
1536
				}
1537
			}
1538
1539
			if ( false === $this->_background_sync() ) {
1540
				// If background sync wasn't executed,
1541
				// and if the plugin declared it has add-ons but
1542
				// no add-ons found in the local data, then try to sync add-ons.
1543
				if ( $this->_has_addons &&
1544
				     ! $this->is_addon() &&
1545
				     ( false === $this->get_addons() )
1546
				) {
1547
					$this->_sync_addons();
1548
				}
1549
			}
1550
1551
			if ( $this->is_addon() ) {
1552
				if ( $this->is_parent_plugin_installed() ) {
1553
					// Link to parent FS.
1554
					$this->_parent = self::get_instance_by_id( $parent_id );
1555
1556
					// Get parent plugin reference.
1557
					$this->_parent_plugin = $this->_parent->get_plugin();
1558
				}
1559
			}
1560
1561
			if ( is_admin() ) {
1562
				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...
1563
				if ( 'plugins.php' === $pagenow ) {
1564
					$this->hook_plugin_action_links();
1565
				}
1566
1567
				if ( $this->is_addon() ) {
1568
					if ( ! $this->is_parent_plugin_installed() ) {
1569
						$this->_admin_notices->add(
1570
							( is_string( $parent_name ) ?
1571
								sprintf( __fs( 'addon-cannot-run-without-x' ), $this->get_plugin_name(), $parent_name ) :
1572
								sprintf( __fs( 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() )
1573
							),
1574
							__fs( 'oops' ) . '...',
1575
							'error'
1576
						);
1577
1578
						return;
1579
					} else {
1580
						if ( $this->_parent->is_registered() && ! $this->is_registered() ) {
1581
							// If parent plugin activated, automatically install add-on for the user.
1582
							$this->_activate_addon_account( $this->_parent );
1583
						}
1584
1585
						// @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic.
1586
						if ( $this->is_premium() ) {
1587
							// Remove add-on download admin-notice.
1588
							$this->_parent->_admin_notices->remove_sticky( 'addon_plan_upgraded_' . $this->_slug );
1589
						}
1590
					}
1591
				} else {
1592
					add_action( 'admin_init', array( &$this, '_admin_init_action' ) );
1593
1594
					if ( $this->_has_addons() &&
1595
					     'plugin-information' === fs_request_get( 'tab', false ) &&
1596
					     $this->get_id() == fs_request_get( 'parent_plugin_id', false )
1597
					) {
1598
						// Remove default plugin information action.
1599
						remove_all_actions( 'install_plugins_pre_plugin-information' );
1600
1601
						require_once WP_FS__DIR_INCLUDES . '/fs-plugin-functions.php';
1602
1603
						// Override action with custom plugins function for add-ons.
1604
						add_action( 'install_plugins_pre_plugin-information', 'fs_install_plugin_information' );
1605
1606
						// Override request for plugin information for Add-ons.
1607
						add_filter( 'fs_plugins_api', array( &$this, '_get_addon_info_filter' ), 10, 3 );
1608
					} else {
1609
						if ( $this->is_paying() || $this->_has_addons() ) {
1610
							new FS_Plugin_Updater( $this );
1611
						}
1612
					}
1613
				}
1614
1615
//				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...
1616
//				     $this->is_anonymous() ||
1617
//				     $this->is_pending_activation()
1618
//				) {
1619
//					$this->_init_admin();
1620
//				}
1621
			}
1622
1623
			$this->do_action( 'initiated' );
1624
1625
			if ( ! $this->is_addon() ) {
1626
				if ( $this->is_registered() ) {
1627
					// Fix for upgrade from versions < 1.0.9.
1628
					if ( ! isset( $this->_storage->activation_timestamp ) ) {
1629
						$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
0 ignored issues
show
Documentation introduced by
The property activation_timestamp 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...
1630
					}
1631
					if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) {
0 ignored issues
show
Documentation introduced by
The property prev_is_premium 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...
1632
						if ( isset( $this->_storage->prev_is_premium ) ) {
1633
							add_action( is_admin() ? 'admin_init' : 'init', array(
1634
								&$this,
1635
								'_plugin_code_type_changed'
1636
							) );
1637
						} else {
1638
							// Set for code type for the first time.
1639
							$this->_storage->prev_is_premium = $this->_plugin->is_premium;
0 ignored issues
show
Documentation introduced by
The property prev_is_premium 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...
1640
						}
1641
					}
1642
1643
					$this->do_action( 'after_init_plugin_registered' );
1644
				} else if ( $this->is_anonymous() ) {
1645
					$this->do_action( 'after_init_plugin_anonymous' );
1646
				} else if ( $this->is_pending_activation() ) {
1647
					$this->do_action( 'after_init_plugin_pending_activations' );
1648
				}
1649
			} else {
1650
				if ( $this->is_registered() ) {
1651
					$this->do_action( 'after_init_addon_registered' );
1652
				} else if ( $this->is_anonymous() ) {
1653
					$this->do_action( 'after_init_addon_anonymous' );
1654
				} else if ( $this->is_pending_activation() ) {
1655
					$this->do_action( 'after_init_addon_pending_activations' );
1656
				}
1657
			}
1658
		}
1659
1660
		/**
1661
		 * Handles plugin's code type change (free <--> premium).
1662
		 *
1663
		 * @author Vova Feldman (@svovaf)
1664
		 * @since  1.0.9
1665
		 */
1666
		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...
1667
			// Send code type changes event.
1668
			$this->sync_install();
1669
1670
			if ( $this->is_premium() ) {
1671
				// Activated premium code.
1672
				$this->do_action( 'after_premium_version_activation' );
1673
1674
				// Remove all sticky messages related to download of the premium version.
1675
				$this->_admin_notices->remove_sticky( array(
1676
					'trial_started',
1677
					'plan_upgraded',
1678
					'plan_changed',
1679
				) );
1680
1681
				$this->_admin_notices->add_sticky(
1682
					__fs( 'premium-activated-message' ),
1683
					'premium_activated',
1684
					__fs( 'woot' ) . '!'
1685
				);
1686
			} else {
1687
				// Activated free code (after had the premium before).
1688
				$this->do_action( 'after_free_version_reactivation' );
1689
1690
				if ( $this->is_paying() && ! $this->is_premium() ) {
1691
					$this->_admin_notices->add_sticky(
1692
						sprintf(
1693
							__fs( 'you-have-x-license' ),
1694
							$this->_site->plan->title
1695
						) . ' ' . $this->_get_latest_download_link( sprintf(
1696
							__fs( 'download-x-version-now' ),
1697
							$this->_site->plan->title
1698
						) ),
1699
						'plan_upgraded',
1700
						__fs( 'yee-haw' ) . '!'
1701
					);
1702
				}
1703
			}
1704
1705
			// Update is_premium of latest version.
1706
			$this->_storage->prev_is_premium = $this->_plugin->is_premium;
0 ignored issues
show
Documentation introduced by
The property prev_is_premium 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...
1707
		}
1708
1709
		#endregion Initialization ------------------------------------------------------------------
1710
1711
		#region Add-ons -------------------------------------------------------------------------
1712
1713
		/**
1714
		 * Generate add-on plugin information.
1715
		 *
1716
		 * @author Vova Feldman (@svovaf)
1717
		 * @since  1.0.6
1718
		 *
1719
		 * @param array       $data
1720
		 * @param string      $action
1721
		 * @param object|null $args
1722
		 *
1723
		 * @return array|null
1724
		 */
1725
		function _get_addon_info_filter( $data, $action = '', $args = 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...
1726
			$this->_logger->entrance();
1727
1728
			$parent_plugin_id = fs_request_get( 'parent_plugin_id', false );
1729
1730
			if ( $this->get_id() != $parent_plugin_id ||
1731
			     ( 'plugin_information' !== $action ) ||
1732
			     ! isset( $args->slug )
1733
			) {
1734
				return $data;
1735
			}
1736
1737
			// Find add-on by slug.
1738
			$addons         = $this->get_addons();
1739
			$selected_addon = false;
1740
			foreach ( $addons as $addon ) {
0 ignored issues
show
Bug introduced by
The expression $addons of type array<integer,object<FS_Plugin>>|false 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...
1741
				if ( $addon->slug == $args->slug ) {
1742
					$selected_addon = $addon;
1743
					break;
1744
				}
1745
			}
1746
1747
			if ( false === $selected_addon ) {
1748
				return $data;
1749
			}
1750
1751
			if ( ! isset( $selected_addon->info ) ) {
1752
				// Setup some default info.
1753
				$selected_addon->info                  = new stdClass();
1754
				$selected_addon->info->selling_point_0 = 'Selling Point 1';
1755
				$selected_addon->info->selling_point_1 = 'Selling Point 2';
1756
				$selected_addon->info->selling_point_2 = 'Selling Point 3';
1757
				$selected_addon->info->description     = '<p>Tell your users all about your add-on</p>';
1758
			}
1759
1760
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
1761
1762
			$data = $args;
1763
1764
			// Fetch as much as possible info from local files.
1765
			$plugin_local_data = $this->get_plugin_data();
1766
			$data->name        = $selected_addon->title;
1767
			$data->author      = $plugin_local_data['Author'];
1768
			$view_vars         = array( 'plugin' => $selected_addon );
1769
			$data->sections    = array(
1770
				'description' => fs_get_template( '/plugin-info/description.php', $view_vars ),
1771
			);
1772
1773
			if ( ! empty( $selected_addon->info->banner_url ) ) {
1774
				$data->banners = array(
1775
					'low' => $selected_addon->info->banner_url,
1776
				);
1777
			}
1778
1779
			if ( ! empty( $selected_addon->info->screenshots ) ) {
1780
				$view_vars                     = array( 'screenshots' => $selected_addon->info->screenshots );
1781
				$data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars );
1782
			}
1783
1784
			// Load add-on pricing.
1785
			$has_pricing  = false;
1786
			$has_features = false;
1787
			$plans        = false;
1788
			$plans_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$selected_addon->id}/plans.json" );
1789
			if ( ! isset( $plans_result->error ) ) {
1790
				$plans = $plans_result->plans;
1791
				if ( is_array( $plans ) ) {
1792
					foreach ( $plans as &$plan ) {
1793
						$pricing_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$selected_addon->id}/plans/{$plan->id}/pricing.json" );
1794
						if ( ! isset( $pricing_result->error ) ) {
1795
							// Update plan's pricing.
1796
							$plan->pricing = $pricing_result->pricing;
1797
1798
							$has_pricing = true;
1799
						}
1800
1801
						$features_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$selected_addon->id}/plans/{$plan->id}/features.json" );
1802
						if ( ! isset( $features_result->error ) &&
1803
						     is_array( $features_result->features ) &&
1804
						     0 < count( $features_result->features )
1805
						) {
1806
							// Update plan's pricing.
1807
							$plan->features = $features_result->features;
1808
1809
							$has_features = true;
1810
						}
1811
					}
1812
				}
1813
			}
1814
1815
			// Get latest add-on version.
1816
			$latest = $this->_fetch_latest_version( $selected_addon->id );
1817
1818
			if ( is_object( $latest ) ) {
1819
				$data->version      = $latest->version;
1820
				$data->last_updated = ! is_null( $latest->updated ) ? $latest->updated : $latest->created;
1821
				$data->requires     = $latest->requires_platform_version;
1822
				$data->tested       = $latest->tested_up_to_version;
1823
			} else {
1824
				// Add dummy version.
1825
				$data->version = '1.0.0';
1826
1827
				// Add message to developer to deploy the plugin through Freemius.
1828
			}
1829
1830
			$data->checkout_link = $this->checkout_url();
1831
			$data->download_link = 'https://dummy.com';
1832
1833
			if ( $has_pricing ) {
1834
				// Add plans to data.
1835
				$data->plans = $plans;
1836
1837
				if ( $has_features ) {
1838
					$view_vars                  = array( 'plans' => $plans );
1839
					$data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars );
1840
				}
1841
			}
1842
1843
			return $data;
1844
		}
1845
1846
		/**
1847
		 * Check if add-on installed and activated on site.
1848
		 *
1849
		 * @author Vova Feldman (@svovaf)
1850
		 * @since  1.0.6
1851
		 *
1852
		 * @param string|number $slug_or_id
1853
		 *
1854
		 * @return bool
1855
		 */
1856
		function is_addon_activated( $slug_or_id ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1857
			return self::has_instance( $slug_or_id );
1858
		}
1859
1860
		/**
1861
		 * Determines if add-on installed.
1862
		 *
1863
		 * NOTE: This is a heuristic and only works if the folder/file named as the slug.
1864
		 *
1865
		 * @author Vova Feldman (@svovaf)
1866
		 * @since  1.0.6
1867
		 *
1868
		 * @param string $slug
1869
		 *
1870
		 * @return bool
1871
		 */
1872
		function is_addon_installed( $slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1873
			return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $slug ) ) );
1874
		}
1875
1876
		/**
1877
		 * Get add-on basename.
1878
		 *
1879
		 * @author Vova Feldman (@svovaf)
1880
		 * @since  1.0.6
1881
		 *
1882
		 * @param string $slug
1883
		 *
1884
		 * @return string
1885
		 */
1886
		function get_addon_basename( $slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1887
			if ( $this->is_addon_activated( $slug ) ) {
1888
				self::instance( $slug )->get_plugin_basename();
0 ignored issues
show
Unused Code introduced by
The call to the method Freemius::get_plugin_basename() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
1889
			}
1890
1891
			return $slug . '/' . $slug . '.php';
1892
		}
1893
1894
		/**
1895
		 * Get installed add-ons instances.
1896
		 *
1897
		 * @author Vova Feldman (@svovaf)
1898
		 * @since  1.0.6
1899
		 *
1900
		 * @return Freemius[]
1901
		 */
1902
		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...
1903
			$installed_addons = array();
1904
			foreach ( self::$_instances as $slug => $instance ) {
1905
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
1906
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
1907
						$installed_addons[] = $instance;
1908
					}
1909
				}
1910
			}
1911
1912
			return $installed_addons;
1913
		}
1914
1915
		/**
1916
		 * Check if any add-ons of the plugin are installed.
1917
		 *
1918
		 * @author Leo Fajardo (@leorw)
1919
		 * @since  1.1.1
1920
		 *
1921
		 * @return bool
1922
		 */
1923
		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...
1924
			if ( ! $this->_has_addons() ) {
1925
				return false;
1926
			}
1927
1928
			foreach ( self::$_instances as $slug => $instance ) {
1929
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
1930
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
1931
						return true;
1932
					}
1933
				}
1934
			}
1935
1936
			return false;
1937
		}
1938
1939
		/**
1940
		 * Tell Freemius that the current plugin is an add-on.
1941
		 *
1942
		 * @author Vova Feldman (@svovaf)
1943
		 * @since  1.0.6
1944
		 *
1945
		 * @param number $parent_plugin_id The parent plugin ID
1946
		 */
1947
		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...
1948
			$this->_plugin->parent_plugin_id = $parent_plugin_id;
1949
		}
1950
1951
		/**
1952
		 * @author Vova Feldman (@svovaf)
1953
		 * @since  1.0.6
1954
		 *
1955
		 * @return bool
1956
		 */
1957
		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...
1958
			return isset( $this->_plugin->parent_plugin_id ) && is_numeric( $this->_plugin->parent_plugin_id );
1959
		}
1960
1961
		#endregion ------------------------------------------------------------------
1962
1963
		#region Sandbox ------------------------------------------------------------------
1964
1965
		/**
1966
		 * Set Freemius into sandbox mode for debugging.
1967
		 *
1968
		 * @author Vova Feldman (@svovaf)
1969
		 * @since  1.0.4
1970
		 *
1971
		 * @param string $secret_key
1972
		 */
1973
		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...
1974
			$this->_plugin->secret_key = $secret_key;
1975
1976
			// Update plugin details.
1977
			FS_Plugin_Manager::instance( $this->_slug )->update( $this->_plugin, true );
1978
		}
1979
1980
		/**
1981
		 * Check if running payments in sandbox mode.
1982
		 *
1983
		 * @author Vova Feldman (@svovaf)
1984
		 * @since  1.0.4
1985
		 *
1986
		 * @return bool
1987
		 */
1988
		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...
1989
			return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key );
1990
		}
1991
1992
		#endregion Sandbox ------------------------------------------------------------------
1993
1994
		/**
1995
		 * Check if running test vs. live plugin.
1996
		 *
1997
		 * @author Vova Feldman (@svovaf)
1998
		 * @since  1.0.5
1999
		 *
2000
		 * @return bool
2001
		 */
2002
		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...
2003
			return $this->_plugin->is_live;
2004
		}
2005
2006
		/**
2007
		 * Check if the user skipped connecting the account with Freemius.
2008
		 *
2009
		 * @author Vova Feldman (@svovaf)
2010
		 * @since  1.0.7
2011
		 *
2012
		 * @return bool
2013
		 */
2014
		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...
2015
			if ( ! isset( $this->_is_anonymous ) ) {
2016
				if ( ! isset( $this->_storage->is_anonymous ) ) {
2017
					// Not skipped.
2018
					$this->_is_anonymous = false;
2019
				} else if ( is_bool( $this->_storage->is_anonymous ) ) {
0 ignored issues
show
Documentation introduced by
The property is_anonymous 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...
2020
					// For back compatibility, since the variable was boolean before.
2021
					$this->_is_anonymous = $this->_storage->is_anonymous;
0 ignored issues
show
Documentation introduced by
The property is_anonymous 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...
2022
2023
					// Upgrade stored data format to 1.1.3 format.
2024
					$this->set_anonymous_mode( $this->_storage->is_anonymous );
0 ignored issues
show
Documentation introduced by
The property is_anonymous 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...
2025
				} else {
2026
					// Version 1.1.3 and higher.
2027
					$this->_is_anonymous = $this->_storage->is_anonymous['is'];
0 ignored issues
show
Documentation introduced by
The property is_anonymous 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...
2028
				}
2029
			}
2030
2031
			return $this->_is_anonymous;
2032
		}
2033
2034
		/**
2035
		 * Check if user connected his account and install pending email activation.
2036
		 *
2037
		 * @author Vova Feldman (@svovaf)
2038
		 * @since  1.0.7
2039
		 *
2040
		 * @return bool
2041
		 */
2042
		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...
2043
			return $this->_storage->get( 'is_pending_activation', false );
2044
		}
2045
2046
		/**
2047
		 * Check if plugin must be WordPress.org compliant.
2048
		 *
2049
		 * @since 1.0.7
2050
		 *
2051
		 * @return bool
2052
		 */
2053
		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...
2054
			return $this->_is_org_compliant;
2055
		}
2056
2057
		/**
2058
		 * Background sync every 24 hours.
2059
		 *
2060
		 * @author Vova Feldman (@svovaf)
2061
		 * @since  1.0.4
2062
		 *
2063
		 * @return bool If function actually executed the sync in this iteration.
2064
		 */
2065
		private function _background_sync() {
2066
			$this->_logger->entrance();
2067
2068
			// Don't sync license on AJAX calls.
2069
			if ( $this->is_ajax() ) {
2070
				return false;
2071
			}
2072
2073
			// Asked to sync explicitly, no need for background sync.
2074
			if ( fs_request_is_action( $this->_slug . '_sync_license' ) ) {
2075
				return false;
2076
			}
2077
2078
			$sync_timestamp = $this->_storage->get( 'sync_timestamp' );
2079
2080
			if ( ! is_numeric( $sync_timestamp ) || $sync_timestamp >= time() ) {
2081
				// If updated not set or happens to be in the future, set as if was 24 hours earlier.
2082
				$sync_timestamp                 = time() - WP_FS__TIME_24_HOURS_IN_SEC;
2083
				$this->_storage->sync_timestamp = $sync_timestamp;
0 ignored issues
show
Documentation introduced by
The property sync_timestamp 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...
2084
			}
2085
2086
			if ( ( defined( 'WP_FS__DEV_MODE' ) && WP_FS__DEV_MODE && fs_request_has( 'background_sync' ) ) ||
2087
			     ( $sync_timestamp <= time() - WP_FS__TIME_24_HOURS_IN_SEC )
2088
			) {
2089
2090
				if ( $this->is_registered() ) {
2091
					// Initiate background plan sync.
2092
					$this->_sync_license( true );
2093
2094
					// Check for plugin updates.
2095
					$this->_check_updates( true );
2096
				}
2097
2098
				if ( ! $this->is_addon() ) {
2099
					if ( $this->is_registered() || $this->_has_addons ) {
2100
						// Try to fetch add-ons if registered or if plugin
2101
						// declared that it has add-ons.
2102
						$this->_sync_addons();
2103
					}
2104
				}
2105
2106
				// Update last sync timestamp.
2107
				$this->_storage->sync_timestamp = time();
0 ignored issues
show
Documentation introduced by
The property sync_timestamp 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...
2108
2109
				return true;
2110
			}
2111
2112
			return false;
2113
		}
2114
2115
		/**
2116
		 * Show a notice that activation is currently pending.
2117
		 *
2118
		 * @author Vova Feldman (@svovaf)
2119
		 * @since  1.0.7
2120
		 *
2121
		 * @param bool|string $email
2122
		 */
2123
		function _add_pending_activation_notice( $email = 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...
2124
			if ( ! is_string( $email ) ) {
2125
				$current_user = wp_get_current_user();
2126
				$email        = $current_user->user_email;
2127
			}
2128
2129
			$this->_admin_notices->add_sticky(
2130
				sprintf(
2131
					__fs( 'pending-activation-message' ),
2132
					'<b>' . $this->get_plugin_name() . '</b>',
2133
					'<b>' . $email . '</b>'
2134
				),
2135
				'activation_pending',
2136
				'Thanks!'
2137
			);
2138
		}
2139
2140
		/**
2141
		 *
2142
		 * NOTE: admin_menu action executed before admin_init.
2143
		 *
2144
		 * @author Vova Feldman (@svovaf)
2145
		 * @since  1.0.7
2146
		 */
2147
		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...
2148
			// Automatically redirect to connect/activation page after plugin activation.
2149
			if ( get_option( "fs_{$this->_slug}_activated", false ) ) {
2150
				delete_option( "fs_{$this->_slug}_activated" );
2151
				$this->_redirect_on_activation_hook();
2152
2153
				return;
2154
			}
2155
2156
			if ( fs_request_is_action( $this->_slug . '_skip_activation' ) ) {
2157
				check_admin_referer( $this->_slug . '_skip_activation' );
2158
2159
				$this->skip_connection();
2160
2161
				if ( fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) ) ) {
2162
					exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _admin_init_action() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2163
				}
2164
			}
2165
2166
			if ( ! $this->is_addon() && ! $this->is_registered() && ! $this->is_anonymous() ) {
2167
				if ( ! $this->is_pending_activation() ) {
2168
					if ( ! $this->_menu->is_activation_page() ) {
2169
						$this->_admin_notices->add(
2170
							sprintf(
2171
								__fs( 'you-are-step-away' ),
2172
								sprintf( '<b><a href="%s">%s</a></b>',
2173
									$this->get_activation_url(),
2174
									sprintf( __fs( 'activate-x-now' ), $this->get_plugin_name() )
2175
								)
2176
							),
2177
							'',
2178
							'update-nag'
2179
						);
2180
					}
2181
				}
2182
			}
2183
2184
			$this->_add_upgrade_action_link();
2185
		}
2186
2187
		/**
2188
		 * Return current page's URL.
2189
		 *
2190
		 * @author Vova Feldman (@svovaf)
2191
		 * @since  1.0.7
2192
		 *
2193
		 * @return string
2194
		 */
2195
		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...
2196
			$url = 'http';
2197
2198
			if ( isset( $_SERVER["HTTPS"] ) ) {
2199
				if ( $_SERVER["HTTPS"] == "on" ) {
2200
					$url .= "s";
2201
				}
2202
			}
2203
			$url .= "://";
2204
			if ( $_SERVER["SERVER_PORT"] != "80" ) {
2205
				$url .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
2206
			} else {
2207
				$url .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
2208
			}
2209
2210
			return esc_url( $url );
2211
		}
2212
2213
		/**
2214
		 * Check if the current page is the plugin's main admin settings page.
2215
		 *
2216
		 * @author Vova Feldman (@svovaf)
2217
		 * @since  1.0.7
2218
		 *
2219
		 * @return bool
2220
		 */
2221
		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...
2222
			return fs_is_plugin_page( $this->_menu->get_raw_slug() ) ||
2223
			       fs_is_plugin_page( $this->_slug );
2224
		}
2225
2226
		/* Events
2227
		------------------------------------------------------------------------------------------------------------------*/
2228
		/**
2229
		 * Delete site install from Database.
2230
		 *
2231
		 * @author Vova Feldman (@svovaf)
2232
		 * @since  1.0.1
2233
		 *
2234
		 * @param bool $store
2235
		 */
2236
		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...
2237
			$sites = self::get_all_sites();
2238
2239
			if ( isset( $sites[ $this->_slug ] ) ) {
2240
				unset( $sites[ $this->_slug ] );
2241
			}
2242
2243
			self::$_accounts->set_option( 'sites', $sites, $store );
2244
		}
2245
2246
		/**
2247
		 * Delete plugin's plans information.
2248
		 *
2249
		 * @param bool $store Flush to Database if true.
2250
		 *
2251
		 * @author Vova Feldman (@svovaf)
2252
		 * @since  1.0.9
2253
		 */
2254
		private function _delete_plans( $store = true ) {
2255
			$this->_logger->entrance();
2256
2257
			$plans = self::get_all_plans();
2258
2259
			unset( $plans[ $this->_slug ] );
2260
2261
			self::$_accounts->set_option( 'plans', $plans, $store );
2262
		}
2263
2264
		/**
2265
		 * Delete all plugin licenses.
2266
		 *
2267
		 * @author Vova Feldman (@svovaf)
2268
		 * @since  1.0.9
2269
		 *
2270
		 * @param bool        $store
2271
		 * @param string|bool $plugin_slug
2272
		 */
2273
		private function _delete_licenses( $store = true, $plugin_slug = false ) {
2274
			$this->_logger->entrance();
2275
2276
			$all_licenses = self::get_all_licenses();
2277
2278
			if ( ! is_string( $plugin_slug ) ) {
2279
				$plugin_slug = $this->_slug;
2280
			}
2281
2282
			unset( $all_licenses[ $plugin_slug ] );
2283
2284
			self::$_accounts->set_option( 'licenses', $all_licenses, $store );
2285
		}
2286
2287
		/**
2288
		 * Plugin activated hook.
2289
		 *
2290
		 * @author Vova Feldman (@svovaf)
2291
		 * @since  1.0.1
2292
		 * @uses   FS_Api
2293
		 */
2294
		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...
2295
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2296
2297
			if ( ! current_user_can( 'activate_plugins' ) ) {
2298
				return;
2299
			}
2300
2301
			// Clear API cache on activation.
2302
			FS_Api::clear_cache();
2303
2304
			if ( $this->is_registered() ) {
2305
				// Send re-activation event and sync.
2306
				$this->sync_install( array(), true );
2307
2308
				/**
2309
				 * @todo Work on automatic deactivation of the Free plugin version. It doesn't work since the slug of the free & premium versions is identical. Therefore, only one instance of Freemius is created and the activation hook of the premium version is not being added.
2310
				 */
2311
				if ( $this->_plugin_basename !== $this->_free_plugin_basename ) {
2312
					// Deactivate Free plugin version on premium plugin activation.
2313
					deactivate_plugins( $this->_free_plugin_basename );
2314
2315
					$this->_admin_notices->add(
2316
						sprintf( __fs( 'successful-version-upgrade-message' ), sprintf( '<b>%s</b>', $this->_plugin->title ) ),
2317
						__fs( 'woot' ) . '!'
2318
					);
2319
				}
2320
			} else if ( $this->is_anonymous() ) {
2321
				/**
2322
				 * Reset "skipped" click cache on the following:
2323
				 *  1. Development mode.
2324
				 *  2. If the user skipped the exact same version before.
2325
				 *
2326
				 * @todo 3. If explicitly asked to retry after every activation.
2327
				 */
2328
				if ( WP_FS__DEV_MODE ||
2329
				     $this->get_plugin_version() == $this->_storage->is_anonymous['version']
0 ignored issues
show
Documentation introduced by
The property is_anonymous 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...
2330
				) {
2331
					$this->reset_anonymous_mode();
2332
				}
2333
			}
2334
2335
			if ( $this->has_api_connectivity() ) {
2336
				// Store hint that the plugin was just activated to enable auto-redirection to settings.
2337
				add_option( "fs_{$this->_slug}_activated", true );
2338
			}
2339
		}
2340
2341
		/**
2342
		 * Delete account.
2343
		 *
2344
		 * @author Vova Feldman (@svovaf)
2345
		 * @since  1.0.3
2346
		 *
2347
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
2348
		 */
2349
		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...
2350
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2351
2352
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
2353
				return;
2354
			}
2355
2356
			$this->do_action( 'before_account_delete' );
2357
2358
			// Clear all admin notices.
2359
			$this->_admin_notices->clear_all_sticky();
2360
2361
			$this->_delete_site( false );
2362
2363
			$this->_delete_plans( false );
2364
2365
			$this->_delete_licenses( false );
2366
2367
			// Delete add-ons related to plugin's account.
2368
			$this->_delete_account_addons( false );
2369
2370
			// @todo Delete plans and licenses of add-ons.
2371
2372
			self::$_accounts->store();
2373
2374
			// Clear all storage data.
2375
			$this->_storage->clear_all( true, array(
2376
				'connectivity_test',
2377
				'is_on',
2378
			) );
2379
2380
			// Send delete event.
2381
			$this->get_api_site_scope()->call( '/', 'delete' );
2382
2383
			$this->do_action( 'after_account_delete' );
2384
		}
2385
2386
		/**
2387
		 * Plugin deactivation hook.
2388
		 *
2389
		 * @author Vova Feldman (@svovaf)
2390
		 * @since  1.0.1
2391
		 */
2392
		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...
2393
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2394
2395
			if ( ! current_user_can( 'activate_plugins' ) ) {
2396
				return;
2397
			}
2398
2399
			$this->_admin_notices->clear_all_sticky();
2400
2401
			if ( ! $this->has_api_connectivity() ) {
2402
				// Reset connectivity test cache.
2403
				unset( $this->_storage->connectivity_test );
2404
			}
2405
2406
			if ( $this->is_registered() ) {
2407
				// Send deactivation event.
2408
				$this->sync_install( array(
2409
					'is_active' => false,
2410
				) );
2411
			}
2412
2413
			// Clear API cache on deactivation.
2414
			FS_Api::clear_cache();
2415
		}
2416
2417
		/**
2418
		 * @author Vova Feldman (@svovaf)
2419
		 * @since  1.1.3
2420
		 *
2421
		 * @param bool $is_anonymous
2422
		 */
2423
		private function set_anonymous_mode( $is_anonymous = true ) {
2424
			// Store information regarding skip to try and opt-in the user
2425
			// again in the future.
2426
			$this->_storage->is_anonymous = array(
0 ignored issues
show
Documentation introduced by
The property is_anonymous 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...
2427
				'is'        => $is_anonymous,
2428
				'timestamp' => WP_FS__SCRIPT_START_TIME,
2429
				'version'   => $this->get_plugin_version(),
2430
			);
2431
		}
2432
2433
		/**
2434
		 * @author Vova Feldman (@svovaf)
2435
		 * @since  1.1.3
2436
		 */
2437
		private function reset_anonymous_mode() {
2438
			unset( $this->_storage->is_anonymous );
2439
		}
2440
2441
		/**
2442
		 * Skip account connect, and set anonymous mode.
2443
		 *
2444
		 * @author Vova Feldman (@svovaf)
2445
		 * @since  1.1.1
2446
		 */
2447
		private function skip_connection() {
2448
			$this->_logger->entrance();
2449
2450
			$this->set_anonymous_mode();
2451
2452
			// Send anonymous skip event.
2453
			// No user identified info nor any tracking will be sent after the user skips the opt-in.
2454
			$this->get_api_plugin_scope()->call( 'skip.json', 'put', array(
2455
				'uid' => $this->get_anonymous_id(),
2456
			) );
2457
		}
2458
2459
		/**
2460
		 * Plugin version update hook.
2461
		 *
2462
		 * @author Vova Feldman (@svovaf)
2463
		 * @since  1.0.4
2464
		 */
2465
		private function update_plugin_version_event() {
2466
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2467
2468
			$this->_site->version = $this->get_plugin_version();
2469
2470
			// Send update event.
2471
			$site = $this->send_install_update( array(), true );
2472
2473
			if ( false !== $site && ! $this->is_api_error( $site ) ) {
2474
				$this->_store_site( true );
2475
			}
2476
		}
2477
2478
		/**
2479
		 * Update install details.
2480
		 *
2481
		 * @author Vova Feldman (@svovaf)
2482
		 * @since  1.1.2
2483
		 *
2484
		 * @param string[] string $override
2485
		 *
2486
		 * @return array
2487
		 */
2488
		private function get_install_data_for_api( $override = array() ) {
2489
			return array_merge( array(
2490
				'version'                      => $this->get_plugin_version(),
2491
				'is_premium'                   => $this->is_premium(),
2492
				'language'                     => get_bloginfo( 'language' ),
2493
				'charset'                      => get_bloginfo( 'charset' ),
2494
				'platform_version'             => get_bloginfo( 'version' ),
2495
				'programming_language_version' => phpversion(),
2496
				'title'                        => get_bloginfo( 'name' ),
2497
				'url'                          => get_site_url(),
2498
				// Special params.
2499
				'is_active'                    => true,
2500
				'is_uninstalled'               => false,
2501
			), $override );
2502
		}
2503
2504
		/**
2505
		 * Update install only if changed.
2506
		 *
2507
		 * @author Vova Feldman (@svovaf)
2508
		 * @since  1.0.9
2509
		 *
2510
		 * @param string[] string $override
2511
		 * @param bool     $flush
2512
		 *
2513
		 * @return false|object|string
2514
		 */
2515
		private function send_install_update( $override = array(), $flush = false ) {
2516
			$this->_logger->entrance();
2517
2518
			$check_properties = $this->get_install_data_for_api( $override );
2519
2520
			if ( $flush ) {
2521
				$params = $check_properties;
2522
			} else {
2523
				$params           = array();
2524
				$special          = array();
2525
				$special_override = false;
2526
2527
				foreach ( $check_properties as $p => $v ) {
2528
					if ( property_exists( $this->_site, $p ) ) {
2529
						if ( ! empty( $this->_site->{$p} ) &&
2530
						     $this->_site->{$p} != $v
2531
						) {
2532
							$this->_site->{$p} = $v;
2533
							$params[ $p ]      = $v;
2534
						}
2535
					} else {
2536
						$special[ $p ] = $v;
2537
2538
						if ( isset( $override[ $p ] ) ) {
2539
							$special_override = true;
2540
						}
2541
					}
2542
				}
2543
2544
				if ( $special_override || 0 < count( $params ) ) {
2545
					// Add special params only if has at least one
2546
					// standard param, or if explicitly requested to
2547
					// override a special param or a pram which is not exist
2548
					// in the install object.
2549
					$params = array_merge( $params, $special );
2550
				}
2551
			}
2552
2553
			if ( 0 < count( $params ) ) {
2554
				// Send updated values to FS.
2555
				return $this->get_api_site_scope()->call( '/', 'put', $params );
2556
			}
2557
2558
			return false;
2559
		}
2560
2561
		/**
2562
		 * Update install only if changed.
2563
		 *
2564
		 * @author Vova Feldman (@svovaf)
2565
		 * @since  1.0.9
2566
		 *
2567
		 * @param string[] string $override
2568
		 * @param bool     $flush
2569
		 *
2570
		 * @return false|object|string
2571
		 */
2572
		private function sync_install( $override = array(), $flush = false ) {
2573
			$this->_logger->entrance();
2574
2575
			$site = $this->send_install_update( $override, $flush );
2576
2577
			if ( false === $site ) {
2578
				// No sync required.
2579
				return;
2580
			}
2581
2582
			if ( $this->is_api_error( $site ) ) {
2583
				// Failed to sync, don't update locally.
2584
				return;
2585
			}
2586
2587
			$plan              = $this->get_plan();
2588
			$this->_site       = new FS_Site( $site );
2589
			$this->_site->plan = $plan;
2590
2591
			$this->_store_site( true );
2592
		}
2593
2594
		/**
2595
		 * Plugin uninstall hook.
2596
		 *
2597
		 * @author Vova Feldman (@svovaf)
2598
		 * @since  1.0.1
2599
		 *
2600
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
2601
		 */
2602
		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...
2603
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2604
2605
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
2606
				return;
2607
			}
2608
2609
			$params = array();
2610
			if ( isset( $this->_storage->uninstall_reason ) ) {
2611
				$params['reason_id']   = $this->_storage->uninstall_reason->id;
0 ignored issues
show
Documentation introduced by
The property uninstall_reason 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...
2612
				$params['reason_info'] = $this->_storage->uninstall_reason->info;
0 ignored issues
show
Documentation introduced by
The property uninstall_reason 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...
2613
			}
2614
2615
			if ( ! $this->is_registered() && isset( $this->_storage->uninstall_reason ) ) {
2616
				// Send anonymous uninstall event only if user submitted a feedback.
2617
				$params['uid'] = $this->get_anonymous_id();
2618
				$this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params );
2619
			} else {
2620
				// Send uninstall event.
2621
				$this->send_install_update( array_merge( $params, array(
2622
					'is_active'      => false,
2623
					'is_uninstalled' => true,
2624
				) ) );
2625
			}
2626
2627
			// @todo Decide if we want to delete plugin information from db.
2628
		}
2629
2630
		/**
2631
		 * @author Vova Feldman (@svovaf)
2632
		 * @since  1.1.1
2633
		 *
2634
		 * @return string
2635
		 */
2636
		private function premium_plugin_basename() {
2637
			return preg_replace( '/\//', '-premium/', $this->_free_plugin_basename, 1 );
2638
		}
2639
2640
		/**
2641
		 * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking.
2642
		 *
2643
		 * @author Vova Feldman (@svovaf)
2644
		 * @since  1.0.2
2645
		 */
2646
		public static function _uninstall_plugin_hook() {
2647
			self::_load_required_static();
2648
2649
			self::$_static_logger->entrance();
2650
2651
			if ( ! current_user_can( 'activate_plugins' ) ) {
2652
				return;
2653
			}
2654
2655
			$plugin_file = substr( current_filter(), strlen( 'uninstall_' ) );
2656
2657
			self::$_static_logger->info( 'plugin = ' . $plugin_file );
2658
2659
			define( 'WP_FS__UNINSTALL_MODE', true );
2660
2661
			$fs = self::get_instance_by_file( $plugin_file );
2662
2663
			if ( is_object( $fs ) ) {
2664
				self::require_plugin_essentials();
2665
2666
				if ( is_plugin_active( $fs->_free_plugin_basename ) ||
2667
				     is_plugin_active( $fs->premium_plugin_basename() )
2668
				) {
2669
					// Deleting Free or Premium plugin version while the other version still installed.
2670
					return;
2671
				}
2672
2673
				$fs->_uninstall_plugin_event();
2674
2675
				$fs->do_action( 'after_uninstall' );
2676
			}
2677
		}
2678
2679
		#region Plugin Information ------------------------------------------------------------------
2680
2681
		/**
2682
		 * Load WordPress core plugin.php essential module.
2683
		 *
2684
		 * @author Vova Feldman (@svovaf)
2685
		 * @since  1.1.1
2686
		 */
2687
		private static function require_plugin_essentials() {
2688
			if ( ! function_exists( 'get_plugins' ) ) {
2689
				require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
2690
			}
2691
		}
2692
2693
		/**
2694
		 * Load WordPress core pluggable.php module.
2695
		 *
2696
		 * @author Vova Feldman (@svovaf)
2697
		 * @since  1.1.2
2698
		 */
2699
		private static function require_pluggable_essentials() {
2700
			if ( ! function_exists( 'wp_get_current_user' ) ) {
2701
				require_once( ABSPATH . 'wp-includes/pluggable.php' );
2702
			}
2703
		}
2704
2705
		/**
2706
		 * Return plugin data.
2707
		 *
2708
		 * @author Vova Feldman (@svovaf)
2709
		 * @since  1.0.1
2710
		 *
2711
		 * @return array
2712
		 */
2713
		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...
2714
			if ( ! isset( $this->_plugin_data ) ) {
2715
				self::require_plugin_essentials();
2716
2717
				$this->_plugin_data = get_plugin_data( $this->_plugin_main_file_path );
2718
			}
2719
2720
			return $this->_plugin_data;
2721
		}
2722
2723
		/**
2724
		 * @author Vova Feldman (@svovaf)
2725
		 * @since  1.0.1
2726
		 *
2727
		 * @return string Plugin slug.
2728
		 */
2729
		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...
2730
			return $this->_slug;
2731
		}
2732
2733
		/**
2734
		 * @author Vova Feldman (@svovaf)
2735
		 * @since  1.0.1
2736
		 *
2737
		 * @return number Plugin ID.
2738
		 */
2739
		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...
2740
			return $this->_plugin->id;
2741
		}
2742
2743
		/**
2744
		 * @author Vova Feldman (@svovaf)
2745
		 * @since  1.0.1
2746
		 *
2747
		 * @return string Plugin public key.
2748
		 */
2749
		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...
2750
			return $this->_plugin->public_key;
2751
		}
2752
2753
		/**
2754
		 * Will be available only on sandbox mode.
2755
		 *
2756
		 * @author Vova Feldman (@svovaf)
2757
		 * @since  1.0.4
2758
		 *
2759
		 * @return mixed Plugin secret key.
2760
		 */
2761
		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...
2762
			return $this->_plugin->secret_key;
2763
		}
2764
2765
		/**
2766
		 * @author Vova Feldman (@svovaf)
2767
		 * @since  1.1.1
2768
		 *
2769
		 * @return bool
2770
		 */
2771
		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...
2772
			return ! empty( $this->_plugin->secret_key );
2773
		}
2774
2775
		/**
2776
		 * @author Vova Feldman (@svovaf)
2777
		 * @since  1.0.9
2778
		 *
2779
		 * @return string
2780
		 */
2781
		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...
2782
			$this->_logger->entrance();
2783
2784
			if ( ! isset( $this->_plugin_name ) ) {
2785
				$plugin_data = $this->get_plugin_data();
2786
2787
				// Get name.
2788
				$this->_plugin_name = $plugin_data['Name'];
2789
2790
				// Check if plugin name contains [Premium] suffix and remove it.
2791
				$suffix     = '[premium]';
2792
				$suffix_len = strlen( $suffix );
2793
2794
				if ( strlen( $plugin_data['Name'] ) > $suffix_len &&
2795
				     $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len )
2796
				) {
2797
					$this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len );
2798
				}
2799
2800
				$this->_logger->departure( 'Name = ' . $this->_plugin_name );
2801
			}
2802
2803
			return $this->_plugin_name;
2804
		}
2805
2806
		/**
2807
		 * @author Vova Feldman (@svovaf)
2808
		 * @since  1.0.0
2809
		 *
2810
		 * @return string
2811
		 */
2812
		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...
2813
			$this->_logger->entrance();
2814
2815
			$plugin_data = $this->get_plugin_data();
2816
2817
			$this->_logger->departure( 'Version = ' . $plugin_data['Version'] );
2818
2819
			return $plugin_data['Version'];
2820
		}
2821
2822
		/**
2823
		 * @author Vova Feldman (@svovaf)
2824
		 * @since  1.0.4
2825
		 *
2826
		 * @return string
2827
		 */
2828
		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...
2829
			return $this->_plugin_basename;
2830
		}
2831
2832
		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...
2833
			$this->_logger->entrance();
2834
2835
			$plugin_folder = $this->_plugin_basename;
2836
2837
			while ( '.' !== dirname( $plugin_folder ) ) {
2838
				$plugin_folder = dirname( $plugin_folder );
2839
			}
2840
2841
			$this->_logger->departure( 'Folder Name = ' . $plugin_folder );
2842
2843
			return $plugin_folder;
2844
		}
2845
2846
		#endregion ------------------------------------------------------------------
2847
2848
		/* Account
2849
		------------------------------------------------------------------------------------------------------------------*/
2850
2851
		/**
2852
		 * Find plugin's slug by plugin's basename.
2853
		 *
2854
		 * @author Vova Feldman (@svovaf)
2855
		 * @since  1.0.9
2856
		 *
2857
		 * @param string $plugin_base_name
2858
		 *
2859
		 * @return false|string
2860
		 */
2861
		private static function find_slug_by_basename( $plugin_base_name ) {
2862
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
2863
2864
			if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) {
2865
				return false;
2866
			}
2867
2868
			return $file_slug_map[ $plugin_base_name ];
2869
		}
2870
2871
		/**
2872
		 * Store the map between the plugin's basename to the slug.
2873
		 *
2874
		 * @author Vova Feldman (@svovaf)
2875
		 * @since  1.0.9
2876
		 */
2877
		private function store_file_slug_map() {
2878
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
2879
2880
			if ( ! array( $file_slug_map ) ) {
2881
				$file_slug_map = array();
2882
			}
2883
2884
			if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) ||
2885
			     $file_slug_map[ $this->_plugin_basename ] !== $this->_slug
2886
			) {
2887
				$file_slug_map[ $this->_plugin_basename ] = $this->_slug;
2888
				self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true );
2889
			}
2890
		}
2891
2892
		/**
2893
		 * @return FS_User[]
2894
		 */
2895
		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...
2896
			$users = self::$_accounts->get_option( 'users', array() );
2897
2898
			if ( ! is_array( $users ) ) {
2899
				$users = array();
2900
			}
2901
2902
			return $users;
2903
		}
2904
2905
		/**
2906
		 * @return FS_Site[]
2907
		 */
2908
		private static function get_all_sites() {
2909
			$sites = self::$_accounts->get_option( 'sites', array() );
2910
2911
			if ( ! is_array( $sites ) ) {
2912
				$sites = array();
2913
			}
2914
2915
			return $sites;
2916
		}
2917
2918
		/**
2919
		 * @author Vova Feldman (@svovaf)
2920
		 * @since  1.0.6
2921
		 *
2922
		 * @return FS_Plugin_License[]
2923
		 */
2924
		private static function get_all_licenses() {
2925
			$licenses = self::$_accounts->get_option( 'licenses', array() );
2926
2927
			if ( ! is_array( $licenses ) ) {
2928
				$licenses = array();
2929
			}
2930
2931
			return $licenses;
2932
		}
2933
2934
		/**
2935
		 * @return FS_Plugin_Plan[]
2936
		 */
2937
		private static function get_all_plans() {
2938
			$plans = self::$_accounts->get_option( 'plans', array() );
2939
2940
			if ( ! is_array( $plans ) ) {
2941
				$plans = array();
2942
			}
2943
2944
			return $plans;
2945
		}
2946
2947
		/**
2948
		 * @author Vova Feldman (@svovaf)
2949
		 * @since  1.0.4
2950
		 *
2951
		 * @return FS_Plugin_Tag[]
2952
		 */
2953
		private static function get_all_updates() {
2954
			$updates = self::$_accounts->get_option( 'updates', array() );
2955
2956
			if ( ! is_array( $updates ) ) {
2957
				$updates = array();
2958
			}
2959
2960
			return $updates;
2961
		}
2962
2963
		/**
2964
		 * @author Vova Feldman (@svovaf)
2965
		 * @since  1.0.6
2966
		 *
2967
		 * @return FS_Plugin[]|false
2968
		 */
2969
		private static function get_all_addons() {
2970
			$addons = self::$_accounts->get_option( 'addons', array() );
2971
2972
			if ( ! is_array( $addons ) ) {
2973
				$addons = array();
2974
			}
2975
2976
			return $addons;
2977
		}
2978
2979
		/**
2980
		 * @author Vova Feldman (@svovaf)
2981
		 * @since  1.0.6
2982
		 *
2983
		 * @return FS_Plugin[]|false
2984
		 */
2985
		private static function get_all_account_addons() {
2986
			$addons = self::$_accounts->get_option( 'account_addons', array() );
2987
2988
			if ( ! is_array( $addons ) ) {
2989
				$addons = array();
2990
			}
2991
2992
			return $addons;
2993
		}
2994
2995
		/**
2996
		 * Check if user is registered.
2997
		 *
2998
		 * @author Vova Feldman (@svovaf)
2999
		 * @since  1.0.1
3000
		 * @return bool
3001
		 */
3002
		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...
3003
			return is_object( $this->_user );
3004
		}
3005
3006
		/**
3007
		 * @author Vova Feldman (@svovaf)
3008
		 * @since  1.0.4
3009
		 *
3010
		 * @return FS_Plugin
3011
		 */
3012
		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...
3013
			return $this->_plugin;
3014
		}
3015
3016
		/**
3017
		 * @author Vova Feldman (@svovaf)
3018
		 * @since  1.0.3
3019
		 *
3020
		 * @return FS_User
3021
		 */
3022
		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...
3023
			return $this->_user;
3024
		}
3025
3026
		/**
3027
		 * @author Vova Feldman (@svovaf)
3028
		 * @since  1.0.3
3029
		 *
3030
		 * @return FS_Site
3031
		 */
3032
		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...
3033
			return $this->_site;
3034
		}
3035
3036
		/**
3037
		 * @author Vova Feldman (@svovaf)
3038
		 * @since  1.0.6
3039
		 *
3040
		 * @return FS_Plugin[]|false
3041
		 */
3042
		function get_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...
3043
			$this->_logger->entrance();
3044
3045
			$addons = self::get_all_addons();
3046
3047
			if ( ! is_array( $addons ) ||
3048
			     ! isset( $addons[ $this->_plugin->id ] ) ||
3049
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
3050
			     0 === count( $addons[ $this->_plugin->id ] )
3051
			) {
3052
				return false;
3053
			}
3054
3055
			return $addons[ $this->_plugin->id ];
3056
		}
3057
3058
		/**
3059
		 * @author Vova Feldman (@svovaf)
3060
		 * @since  1.0.6
3061
		 *
3062
		 * @return FS_Plugin[]|false
3063
		 */
3064
		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...
3065
			$this->_logger->entrance();
3066
3067
			$addons = self::get_all_account_addons();
3068
3069
			if ( ! is_array( $addons ) ||
3070
			     ! isset( $addons[ $this->_plugin->id ] ) ||
3071
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
3072
			     0 === count( $addons[ $this->_plugin->id ] )
3073
			) {
3074
				return false;
3075
			}
3076
3077
			return $addons[ $this->_plugin->id ];
3078
		}
3079
3080
		/**
3081
		 * Get add-on by ID (from local data).
3082
		 *
3083
		 * @author Vova Feldman (@svovaf)
3084
		 * @since  1.0.6
3085
		 *
3086
		 * @param number $id
3087
		 *
3088
		 * @return FS_Plugin|false
3089
		 */
3090
		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...
3091
			$this->_logger->entrance();
3092
3093
			$addons = $this->get_addons();
3094
3095
			if ( is_array( $addons ) ) {
3096
				foreach ( $addons as $addon ) {
3097
					if ( $id == $addon->id ) {
3098
						return $addon;
3099
					}
3100
				}
3101
			}
3102
3103
			return false;
3104
		}
3105
3106
		/**
3107
		 * Get add-on by slug (from local data).
3108
		 *
3109
		 * @author Vova Feldman (@svovaf)
3110
		 * @since  1.0.6
3111
		 *
3112
		 * @param string $slug
3113
		 *
3114
		 * @return FS_Plugin|false
3115
		 */
3116
		function get_addon_by_slug( $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...
3117
			$this->_logger->entrance();
3118
3119
			$addons = $this->get_addons();
3120
3121
			if ( is_array( $addons ) ) {
3122
				foreach ( $addons as $addon ) {
3123
					if ( $slug == $addon->slug ) {
3124
						return $addon;
3125
					}
3126
				}
3127
			}
3128
3129
			return false;
3130
		}
3131
3132
		#region Plans & Licensing ------------------------------------------------------------------
3133
3134
		/**
3135
		 * Check if running premium plugin code.
3136
		 *
3137
		 * @author Vova Feldman (@svovaf)
3138
		 * @since  1.0.5
3139
		 *
3140
		 * @return bool
3141
		 */
3142
		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...
3143
			return $this->_plugin->is_premium;
3144
		}
3145
3146
		/**
3147
		 * Get site's plan ID.
3148
		 *
3149
		 * @author Vova Feldman (@svovaf)
3150
		 * @since  1.0.2
3151
		 *
3152
		 * @return number
3153
		 */
3154
		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...
3155
			return $this->_site->plan->id;
3156
		}
3157
3158
		/**
3159
		 * Get site's plan title.
3160
		 *
3161
		 * @author Vova Feldman (@svovaf)
3162
		 * @since  1.0.2
3163
		 *
3164
		 * @return string
3165
		 */
3166
		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...
3167
			return $this->_site->plan->title;
3168
		}
3169
3170
		/**
3171
		 * @author Vova Feldman (@svovaf)
3172
		 * @since  1.0.9
3173
		 *
3174
		 * @return FS_Plugin_Plan
3175
		 */
3176
		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...
3177
			return is_object( $this->_site->plan ) ? $this->_site->plan : false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression is_object($this->_site->...s->_site->plan : false; of type FS_Plugin_Plan|false adds false to the return on line 3177 which is incompatible with the return type documented by Freemius::get_plan of type FS_Plugin_Plan. It seems like you forgot to handle an error condition.
Loading history...
3178
		}
3179
3180
		/**
3181
		 * @author Vova Feldman (@svovaf)
3182
		 * @since  1.0.3
3183
		 *
3184
		 * @return bool
3185
		 */
3186
		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...
3187
			$this->_logger->entrance();
3188
3189
			if ( ! $this->is_registered() ) {
3190
				return false;
3191
			}
3192
3193
			// Paid plan beats trial.
3194
			return $this->is_free_plan() && $this->_site->is_trial();
3195
		}
3196
3197
		/**
3198
		 * Check if trial already utilized.
3199
		 *
3200
		 * @since 1.0.9
3201
		 *
3202
		 * @return bool
3203
		 */
3204
		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...
3205
			$this->_logger->entrance();
3206
3207
			if ( ! $this->is_registered() ) {
3208
				return false;
3209
			}
3210
3211
			return $this->_site->is_trial_utilized();
3212
		}
3213
3214
		/**
3215
		 * Get trial plan information (if in trial).
3216
		 *
3217
		 * @author Vova Feldman (@svovaf)
3218
		 * @since  1.0.9
3219
		 *
3220
		 * @return bool|FS_Plugin_Plan
3221
		 */
3222
		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...
3223
			$this->_logger->entrance();
3224
3225
			if ( ! $this->is_trial() ) {
3226
				return false;
3227
			}
3228
3229
			return $this->_storage->trial_plan;
0 ignored issues
show
Documentation introduced by
The property trial_plan 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...
3230
		}
3231
3232
		/**
3233
		 * Check if the user has an activated and valid paid license on current plugin's install.
3234
		 *
3235
		 * @since 1.0.9
3236
		 *
3237
		 * @return bool
3238
		 */
3239
		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...
3240
			$this->_logger->entrance();
3241
3242
			if ( ! $this->is_registered() ) {
3243
				return false;
3244
			}
3245
3246
			return (
3247
				! $this->is_trial() &&
3248
				'free' !== $this->_site->plan->name &&
3249
				$this->has_features_enabled_license()
3250
			);
3251
		}
3252
3253
		/**
3254
		 * @author Vova Feldman (@svovaf)
3255
		 * @since  1.0.4
3256
		 *
3257
		 * @return bool
3258
		 */
3259
		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...
3260
			if ( ! $this->is_registered() ) {
3261
				return true;
3262
			}
3263
3264
			return (
3265
				'free' === $this->_site->plan->name ||
3266
				! $this->has_features_enabled_license()
3267
			);
3268
		}
3269
3270
		/**
3271
		 * @author Vova Feldman (@svovaf)
3272
		 * @since  1.0.5
3273
		 *
3274
		 * @return bool
3275
		 */
3276
		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...
3277
			$this->_logger->entrance();
3278
3279
			$premium_license = $this->_get_available_premium_license();
3280
3281
			return ( false !== $premium_license );
3282
		}
3283
3284
		/**
3285
		 * @author Vova Feldman (@svovaf)
3286
		 * @since  1.0.5
3287
		 *
3288
		 * @return FS_Plugin_License
3289
		 */
3290
		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...
3291
			$this->_logger->entrance();
3292
3293
			if ( is_array( $this->_licenses ) ) {
3294
				foreach ( $this->_licenses as $license ) {
3295
					if ( ! $license->is_utilized() && $license->is_features_enabled() ) {
3296
						return $license;
3297
					}
3298
				}
3299
			}
3300
3301
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Freemius::_get_available_premium_license of type FS_Plugin_License.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
3302
		}
3303
3304
		/**
3305
		 * Sync local plugin plans with remote server.
3306
		 *
3307
		 * @author Vova Feldman (@svovaf)
3308
		 * @since  1.0.5
3309
		 *
3310
		 * @return FS_Plugin_Plan[]|object
3311
		 */
3312
		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...
3313
			$plans = $this->_fetch_plugin_plans();
3314
			if ( ! $this->is_api_error( $plans ) ) {
3315
				$this->_plans = $plans;
3316
				$this->_store_plans();
3317
			}
3318
3319
			$this->do_action( 'after_plans_sync', $plans );
3320
3321
			return $this->_plans;
3322
		}
3323
3324
		/**
3325
		 * @author Vova Feldman (@svovaf)
3326
		 * @since  1.0.5
3327
		 *
3328
		 * @param number $id
3329
		 *
3330
		 * @return FS_Plugin_Plan
3331
		 */
3332
		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...
3333
			$this->_logger->entrance();
3334
3335
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
3336
				$this->_sync_plans();
3337
			}
3338
3339
			foreach ( $this->_plans as $plan ) {
3340
				if ( $id == $plan->id ) {
3341
					return $plan;
3342
				}
3343
			}
3344
3345
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Freemius::_get_plan_by_id of type FS_Plugin_Plan.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
3346
		}
3347
3348
		/**
3349
		 * Sync local plugin plans with remote server.
3350
		 *
3351
		 * @author Vova Feldman (@svovaf)
3352
		 * @since  1.0.6
3353
		 *
3354
		 * @return FS_Plugin_License[]|object
3355
		 */
3356
		function _sync_licenses() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3357
			$licenses = $this->_fetch_licenses();
3358
			if ( ! isset( $licenses->error ) ) {
3359
				$this->_licenses = $licenses;
3360
				$this->_store_licenses();
3361
			}
3362
3363
			// Update current license.
3364
			if ( is_object( $this->_license ) ) {
3365
				$this->_license = $this->_get_license_by_id( $this->_license->id );
3366
			}
3367
3368
			return $this->_licenses;
3369
		}
3370
3371
		/**
3372
		 * @author Vova Feldman (@svovaf)
3373
		 * @since  1.0.5
3374
		 *
3375
		 * @param number $id
3376
		 *
3377
		 * @return FS_Plugin_License
3378
		 */
3379
		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...
3380
			$this->_logger->entrance();
3381
3382
			if ( ! is_numeric( $id ) ) {
3383
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Freemius::_get_license_by_id of type FS_Plugin_License.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
3384
			}
3385
3386
			if ( ! is_array( $this->_licenses ) || 0 === count( $this->_licenses ) ) {
3387
				$this->_sync_licenses();
3388
			}
3389
3390
			foreach ( $this->_licenses as $license ) {
3391
				if ( $id == $license->id ) {
3392
					return $license;
3393
				}
3394
			}
3395
3396
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Freemius::_get_license_by_id of type FS_Plugin_License.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
3397
		}
3398
3399
		/**
3400
		 * Sync site's license with user licenses.
3401
		 *
3402
		 * @author Vova Feldman (@svovaf)
3403
		 * @since  1.0.6
3404
		 *
3405
		 * @param FS_Plugin_License|null $new_license
3406
		 */
3407
		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...
3408
			$this->_logger->entrance();
3409
3410
			$this->_license = $new_license;
3411
3412
			if ( ! is_object( $new_license ) ) {
3413
				$this->_site->license_id = null;
3414
				$this->_sync_site_subscription( null );
3415
3416
				return;
3417
			}
3418
3419
			$this->_site->license_id = $this->_license->id;
3420
3421
			if ( ! is_array( $this->_licenses ) ) {
3422
				$this->_licenses = array();
3423
			}
3424
3425
			$is_license_found = false;
3426
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
3427
				if ( $new_license->id == $this->_licenses[ $i ]->id ) {
3428
					$this->_licenses[ $i ] = $new_license;
3429
3430
					$is_license_found = true;
3431
					break;
3432
				}
3433
			}
3434
3435
			// If new license just append.
3436
			if ( ! $is_license_found ) {
3437
				$this->_licenses[] = $new_license;
3438
			}
3439
3440
			$this->_sync_site_subscription( $new_license );
3441
		}
3442
3443
		/**
3444
		 * Sync site's subscription.
3445
		 *
3446
		 * @author Vova Feldman (@svovaf)
3447
		 * @since  1.0.9
3448
		 *
3449
		 * @param FS_Plugin_License|null $license
3450
		 *
3451
		 * @return bool|\FS_Subscription
3452
		 */
3453
		private function _sync_site_subscription( $license ) {
3454
			if ( ! is_object( $license ) ) {
3455
				unset( $this->_storage->subscription );
3456
3457
				return false;
3458
			}
3459
3460
			// Load subscription details if not lifetime.
3461
			$subscription = $license->is_lifetime() ?
3462
				false :
3463
				$this->_fetch_site_license_subscription();
3464
3465
			if ( is_object( $subscription ) && ! isset( $subscription->error ) ) {
3466
				$this->_storage->subscription = $subscription;
0 ignored issues
show
Documentation introduced by
The property subscription 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...
3467
			} else {
3468
				unset( $this->_storage->subscription );
3469
			}
3470
3471
			return $subscription;
3472
		}
3473
3474
		/**
3475
		 * @author Vova Feldman (@svovaf)
3476
		 * @since  1.0.6
3477
		 *
3478
		 * @return bool|\FS_Plugin_License
3479
		 */
3480
		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...
3481
			return $this->_license;
3482
		}
3483
3484
		/**
3485
		 * @return bool|\FS_Subscription
3486
		 */
3487
		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...
3488
			return isset( $this->_storage->subscription ) ?
3489
				$this->_storage->subscription :
0 ignored issues
show
Documentation introduced by
The property subscription 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...
3490
				false;
3491
		}
3492
3493
		/**
3494
		 * @author Vova Feldman (@svovaf)
3495
		 * @since  1.0.2
3496
		 *
3497
		 * @param string $plan  Plan name
3498
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
3499
		 *
3500
		 * @return bool
3501
		 */
3502
		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...
3503
			$this->_logger->entrance();
3504
3505
			if ( ! $this->is_registered() ) {
3506
				return false;
3507
			}
3508
3509
			$plan = strtolower( $plan );
3510
3511
			if ( $this->_site->plan->name === $plan ) // Exact plan.
3512
			{
3513
				return true;
3514
			} else if ( $exact ) // Required exact, but plans are different.
3515
			{
3516
				return false;
3517
			}
3518
3519
			$current_plan_order  = - 1;
3520
			$required_plan_order = - 1;
3521
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
3522
				if ( $plan === $this->_plans[ $i ]->name ) {
3523
					$required_plan_order = $i;
3524
				} else if ( $this->_site->plan->name === $this->_plans[ $i ]->name ) {
3525
					$current_plan_order = $i;
3526
				}
3527
			}
3528
3529
			return ( $current_plan_order > $required_plan_order );
3530
		}
3531
3532
		/**
3533
		 * Check if plan based on trial. If not in trial mode, should return false.
3534
		 *
3535
		 * @since  1.0.9
3536
		 *
3537
		 * @param string $plan  Plan name
3538
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
3539
		 *
3540
		 * @return bool
3541
		 */
3542
		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...
3543
			$this->_logger->entrance();
3544
3545
			if ( ! $this->is_registered() ) {
3546
				return false;
3547
			}
3548
3549
			if ( ! $this->is_trial() ) {
3550
				return false;
3551
			}
3552
3553
			if ( ! isset( $this->_storage->trial_plan ) ) {
3554
				// Store trial plan information.
3555
				$this->_enrich_site_trial_plan( true );
3556
			}
3557
3558
			if ( $this->_storage->trial_plan->name === $plan ) // Exact plan.
0 ignored issues
show
Documentation introduced by
The property trial_plan 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...
3559
			{
3560
				return true;
3561
			} else if ( $exact ) // Required exact, but plans are different.
3562
			{
3563
				return false;
3564
			}
3565
3566
			$current_plan_order  = - 1;
3567
			$required_plan_order = - 1;
3568
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
3569
				if ( $plan === $this->_plans[ $i ]->name ) {
3570
					$required_plan_order = $i;
3571
				} else if ( $this->_storage->trial_plan->name === $this->_plans[ $i ]->name ) {
0 ignored issues
show
Documentation introduced by
The property trial_plan 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...
3572
					$current_plan_order = $i;
3573
				}
3574
			}
3575
3576
			return ( $current_plan_order > $required_plan_order );
3577
		}
3578
3579
		/**
3580
		 * Check if plugin has any paid plans.
3581
		 *
3582
		 * @author Vova Feldman (@svovaf)
3583
		 * @since  1.0.7
3584
		 *
3585
		 * @return bool
3586
		 */
3587
		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...
3588
			return $this->_has_paid_plans || FS_Plan_Manager::instance()->has_paid_plan( $this->_plans );
3589
		}
3590
3591
		/**
3592
		 * Check if plugin has any plan with a trail.
3593
		 *
3594
		 * @author Vova Feldman (@svovaf)
3595
		 * @since  1.0.9
3596
		 *
3597
		 * @return bool
3598
		 */
3599
		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...
3600
			if ( ! $this->is_registered() ) {
3601
				return false;
3602
			}
3603
3604
			return $this->_storage->get( 'has_trial_plan', false );
3605
		}
3606
3607
		/**
3608
		 * Check if plugin has any free plan, or is it premium only.
3609
		 *
3610
		 * Note: If no plans configured, assume plugin is free.
3611
		 *
3612
		 * @author Vova Feldman (@svovaf)
3613
		 * @since  1.0.7
3614
		 *
3615
		 * @return bool
3616
		 */
3617
		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...
3618
			return FS_Plan_Manager::instance()->has_free_plan( $this->_plans );
3619
		}
3620
3621
		#region URL Generators
3622
3623
		/**
3624
		 * Alias to pricing_url().
3625
		 *
3626
		 * @author Vova Feldman (@svovaf)
3627
		 * @since  1.0.2
3628
		 *
3629
		 * @uses   pricing_url
3630
		 *
3631
		 * @param string $period Billing cycle
3632
		 *
3633
		 * @return string
3634
		 */
3635
		function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3636
			return $this->pricing_url( $period );
3637
		}
3638
3639
		/**
3640
		 * @author Vova Feldman (@svovaf)
3641
		 * @since  1.0.9
3642
		 *
3643
		 * @uses   get_upgrade_url
3644
		 *
3645
		 * @return string
3646
		 */
3647
		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...
3648
			return $this->get_upgrade_url( 'trial' );
3649
		}
3650
3651
		/**
3652
		 * Plugin's pricing URL.
3653
		 *
3654
		 * @author Vova Feldman (@svovaf)
3655
		 * @since  1.0.4
3656
		 *
3657
		 * @param string $period Billing cycle
3658
		 *
3659
		 * @return string
3660
		 */
3661
		function pricing_url( $period = WP_FS__PERIOD_ANNUALLY ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3662
			$this->_logger->entrance();
3663
3664
			return $this->_get_admin_page_url( 'pricing', array( 'billing_cycle' => $period ) );
3665
		}
3666
3667
		/**
3668
		 * Checkout page URL.
3669
		 *
3670
		 * @author Vova Feldman (@svovaf)
3671
		 * @since  1.0.6
3672
		 *
3673
		 * @param string      $period Billing cycle
3674
		 * @param bool|string $plan_name
3675
		 * @param bool|number $plan_id
3676
		 * @param bool|int    $licenses
3677
		 *
3678
		 * @return string
3679
		 */
3680
		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...
3681
			$period = WP_FS__PERIOD_ANNUALLY,
3682
			$plan_name = false,
3683
			$plan_id = false,
3684
			$licenses = false
3685
		) {
3686
			$this->_logger->entrance();
3687
3688
			$params = array(
3689
				'checkout'      => 'true',
3690
				'billing_cycle' => $period,
3691
			);
3692
3693
			if ( false !== $plan_name ) {
3694
				$params['plan_name'] = $plan_name;
3695
			}
3696
			if ( false !== $plan_id ) {
3697
				$params['plan_id'] = $plan_id;
3698
			}
3699
			if ( false !== $licenses ) {
3700
				$params['licenses'] = $licenses;
3701
			}
3702
3703
			return $this->_get_admin_page_url( 'pricing', $params );
3704
		}
3705
3706
		#endregion
3707
3708
		#endregion ------------------------------------------------------------------
3709
3710
		/**
3711
		 * Check if plugin has any add-ons.
3712
		 *
3713
		 * @author Vova Feldman (@svovaf)
3714
		 * @since  1.0.5
3715
		 *
3716
		 * @return bool
3717
		 */
3718
		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...
3719
			$this->_logger->entrance();
3720
3721
			return ( $this->_has_addons || false !== $this->get_addons() );
3722
		}
3723
3724
		/**
3725
		 * Check if plugin can work in anonymous mode.
3726
		 *
3727
		 * @author Vova Feldman (@svovaf)
3728
		 * @since  1.0.9
3729
		 *
3730
		 * @return bool
3731
		 */
3732
		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...
3733
			return $this->_enable_anonymous;
3734
		}
3735
3736
		/**
3737
		 * Check if feature supported with current site's plan.
3738
		 *
3739
		 * @author Vova Feldman (@svovaf)
3740
		 * @since  1.0.1
3741
		 *
3742
		 * @todo   IMPLEMENT
3743
		 *
3744
		 * @param number $feature_id
3745
		 *
3746
		 * @throws Exception
3747
		 */
3748
		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...
3749
			throw new Exception( 'not implemented' );
3750
		}
3751
3752
		/**
3753
		 * @author Vova Feldman (@svovaf)
3754
		 * @since  1.0.1
3755
		 *
3756
		 * @return bool Is running in SSL/HTTPS
3757
		 */
3758
		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...
3759
			return WP_FS__IS_HTTPS;
3760
		}
3761
3762
		/**
3763
		 * @author Vova Feldman (@svovaf)
3764
		 * @since  1.0.9
3765
		 *
3766
		 * @return bool Is running in AJAX call.
3767
		 *
3768
		 * @link   http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax
3769
		 */
3770
		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...
3771
			return ( defined( 'DOING_AJAX' ) && DOING_AJAX );
3772
		}
3773
3774
		/**
3775
		 * Check if running in HTTPS and if site's plan matching the specified plan.
3776
		 *
3777
		 * @param string $plan
3778
		 * @param bool   $exact
3779
		 *
3780
		 * @return bool
3781
		 */
3782
		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...
3783
			return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) );
3784
		}
3785
3786
		/**
3787
		 * Construct plugin's settings page URL.
3788
		 *
3789
		 * @author Vova Feldman (@svovaf)
3790
		 * @since  1.0.4
3791
		 *
3792
		 * @param string $page
3793
		 * @param array  $params
3794
		 *
3795
		 * @return string
3796
		 */
3797
		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...
3798
			if ( empty( $page ) && ! $this->_menu->is_top_level() ) {
3799
				// If not a Top-Level menu and asking for main settings page,
3800
				// then try to replicate plugin's main setting original page URL.
3801
				switch ( $this->_menu->get_type() ) {
3802
					case 'tools':
3803
						return add_query_arg( array(
3804
							'page' => $this->_menu->get_raw_slug(),
3805
						), admin_url( 'tools.php' ) );
3806
					case 'settings':
3807
						return add_query_arg( array(
3808
							'page' => $this->_menu->get_raw_slug(),
3809
						), admin_url( 'options-general.php' ) );
3810
				}
3811
			}
3812
3813
			if ( $this->_menu->is_cpt() ) {
3814
				if ( empty( $page ) && $this->is_activation_mode() ) {
3815
					return add_query_arg( array_merge( $params, array(
3816
						'page' => $this->_menu->get_raw_slug()
3817
					) ), admin_url( 'admin.php', 'admin' ) );
3818
				} else {
3819
					if ( ! empty( $page ) ) {
3820
						$params['page'] = trim( "{$this->_menu->get_raw_slug()}-{$page}", '-' );
3821
					}
3822
3823
					return add_query_arg( array_merge( $params, array(
3824
						'post_type' => $this->_menu->get_raw_slug(),
3825
					) ), admin_url( 'edit.php', 'admin' ) );
3826
				}
3827
			} else if ( false === strpos( $this->_menu->get_raw_slug(), '.php?' ) ) {
3828
				return add_query_arg( array_merge( $params, array(
3829
					'page' => trim( "{$this->_menu->get_raw_slug()}-{$page}", '-' )
3830
				) ), admin_url( 'admin.php', 'admin' ) );
3831
			} else {
3832
				return add_query_arg( array_merge( $params, array(
3833
					'page' => trim( "{$this->_slug}-{$page}", '-' )
3834
				) ), admin_url( 'admin.php', 'admin' ) );
3835
			}
3836
		}
3837
3838
3839
		/**
3840
		 * Plugin's account URL.
3841
		 *
3842
		 * @author Vova Feldman (@svovaf)
3843
		 * @since  1.0.4
3844
		 *
3845
		 * @param bool|string $action
3846
		 * @param array       $params
3847
		 *
3848
		 * @param bool        $add_action_nonce
3849
		 *
3850
		 * @return string
3851
		 */
3852
		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...
3853
			if ( is_string( $action ) ) {
3854
				$params['fs_action'] = $action;
3855
			}
3856
3857
			self::require_pluggable_essentials();
3858
3859
			return ( $add_action_nonce && is_string( $action ) ) ?
3860
				wp_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) :
3861
				$this->_get_admin_page_url( 'account', $params );
3862
		}
3863
3864
		/**
3865
		 * Plugin's account URL.
3866
		 *
3867
		 * @author Vova Feldman (@svovaf)
3868
		 * @since  1.0.4
3869
		 *
3870
		 * @param bool|string $topic
3871
		 * @param bool|string $message
3872
		 *
3873
		 * @return string
3874
		 */
3875
		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...
3876
			$params = array();
3877
			if ( is_string( $topic ) ) {
3878
				$params['topic'] = $topic;
3879
			}
3880
			if ( is_string( $message ) ) {
3881
				$params['message'] = $message;
3882
			}
3883
3884
			if ( $this->is_addon() ) {
3885
				$params['addon_id'] = $this->get_id();
3886
3887
				return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params );
3888
			} else {
3889
				return $this->_get_admin_page_url( 'contact', $params );
3890
			}
3891
		}
3892
3893
		/**
3894
		 * Add-on direct info URL.
3895
		 *
3896
		 * @author Vova Feldman (@svovaf)
3897
		 * @since  1.1.0
3898
		 *
3899
		 * @param string $slug
3900
		 *
3901
		 * @return string
3902
		 */
3903
		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...
3904
			return $this->_get_admin_page_url( 'addons', array(
3905
				'slug' => $slug
3906
			) );
3907
		}
3908
3909
		/* Logger
3910
		------------------------------------------------------------------------------------------------------------------*/
3911
		/**
3912
		 * @param string $id
3913
		 * @param bool   $prefix_slug
3914
		 *
3915
		 * @return FS_Logger
3916
		 */
3917
		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...
3918
			return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id );
3919
		}
3920
3921
		/**
3922
		 * @param      $id
3923
		 * @param bool $load_options
3924
		 * @param bool $prefix_slug
3925
		 *
3926
		 * @return FS_Option_Manager
3927
		 */
3928
		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...
3929
			return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options );
3930
		}
3931
3932
		/* Security
3933
		------------------------------------------------------------------------------------------------------------------*/
3934
		private function _encrypt( $str ) {
3935
			if ( is_null( $str ) ) {
3936
				return null;
3937
			}
3938
3939
			return base64_encode( $str );
3940
		}
3941
3942
		private function _decrypt( $str ) {
3943
			if ( is_null( $str ) ) {
3944
				return null;
3945
			}
3946
3947
			return base64_decode( $str );
3948
		}
3949
3950
		/**
3951
		 * @author Vova Feldman (@svovaf)
3952
		 * @since  1.0.5
3953
		 *
3954
		 * @param FS_Entity $entity
3955
		 *
3956
		 * @return FS_Entity Return an encrypted clone entity.
3957
		 */
3958
		private function _encrypt_entity( FS_Entity $entity ) {
3959
			$clone = clone $entity;
3960
			$props = get_object_vars( $entity );
3961
3962
			foreach ( $props as $key => $val ) {
3963
				$clone->{$key} = $this->_encrypt( $val );
3964
			}
3965
3966
			return $clone;
3967
		}
3968
3969
		/**
3970
		 * @author Vova Feldman (@svovaf)
3971
		 * @since  1.0.5
3972
		 *
3973
		 * @param FS_Entity $entity
3974
		 *
3975
		 * @return FS_Entity Return an decrypted clone entity.
3976
		 */
3977
		private function _decrypt_entity( FS_Entity $entity ) {
3978
			$clone = clone $entity;
3979
			$props = get_object_vars( $entity );
3980
3981
			foreach ( $props as $key => $val ) {
3982
				$clone->{$key} = $this->_decrypt( $val );
3983
			}
3984
3985
			return $clone;
3986
		}
3987
3988
		/**
3989
		 * Tries to activate account based on POST params.
3990
		 *
3991
		 * @author Vova Feldman (@svovaf)
3992
		 * @since  1.0.2
3993
		 */
3994
		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...
3995
			if ( $this->is_registered() ) {
3996
				// Already activated.
3997
				return;
3998
			}
3999
4000
			$this->_clean_admin_content_section();
4001
4002
			if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) {
4003
//				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...
4004
4005
				// Verify matching plugin details.
4006
				if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) {
4007
					return;
4008
				}
4009
4010
				$user              = new FS_User();
4011
				$user->id          = fs_request_get( 'user_id' );
4012
				$user->public_key  = fs_request_get( 'user_public_key' );
4013
				$user->secret_key  = fs_request_get( 'user_secret_key' );
4014
				$user->email       = fs_request_get( 'user_email' );
4015
				$user->first       = fs_request_get( 'user_first' );
4016
				$user->last        = fs_request_get( 'user_last' );
4017
				$user->is_verified = fs_request_get_bool( 'user_is_verified' );
4018
4019
				$site              = new FS_Site();
4020
				$site->id          = fs_request_get( 'install_id' );
4021
				$site->public_key  = fs_request_get( 'install_public_key' );
4022
				$site->secret_key  = fs_request_get( 'install_secret_key' );
4023
				$site->plan->id    = fs_request_get( 'plan_id' );
4024
				$site->plan->title = fs_request_get( 'plan_title' );
4025
				$site->plan->name  = fs_request_get( 'plan_name' );
4026
4027
				$plans      = array();
4028
				$plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) );
4029
				foreach ( $plans_data as $p ) {
4030
					$plans[] = new FS_Plugin_Plan( $p );
4031
				}
4032
4033
				$this->_set_account( $user, $site, $plans );
4034
4035
				// Reload the page with the keys.
4036
				if ( fs_redirect( $this->_get_admin_page_url() ) ) {
4037
					exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _activate_account() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
4038
				}
4039
			}
4040
		}
4041
4042
		/**
4043
		 * @author Vova Feldman (@svovaf)
4044
		 * @since  1.0.7
4045
		 *
4046
		 * @param string $email
4047
		 *
4048
		 * @return FS_User|bool
4049
		 */
4050
		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...
4051
			self::$_static_logger->entrance();
4052
4053
			$email = trim( strtolower( $email ) );
4054
			$users = self::get_all_users();
4055
			if ( is_array( $users ) ) {
4056
				foreach ( $users as $u ) {
4057
					if ( $email === trim( strtolower( $u->email ) ) ) {
4058
						return $u;
4059
					}
4060
				}
4061
			}
4062
4063
			return false;
4064
		}
4065
4066
		#region Account (Loading, Updates & Activation) ------------------------------------------------------------------
4067
4068
		/***
4069
		 * Load account information (user + site).
4070
		 *
4071
		 * @author Vova Feldman (@svovaf)
4072
		 * @since  1.0.1
4073
		 */
4074
		private function _load_account() {
4075
			$this->_logger->entrance();
4076
4077
			$this->do_action( 'before_account_load' );
4078
4079
			$sites    = self::get_all_sites();
4080
			$users    = self::get_all_users();
4081
			$plans    = self::get_all_plans();
4082
			$licenses = self::get_all_licenses();
4083
4084
			if ( $this->_logger->is_on() && is_admin() ) {
4085
				$this->_logger->log( 'sites = ' . var_export( $sites, true ) );
4086
				$this->_logger->log( 'users = ' . var_export( $users, true ) );
4087
				$this->_logger->log( 'plans = ' . var_export( $plans, true ) );
4088
				$this->_logger->log( 'licenses = ' . var_export( $licenses, true ) );
4089
			}
4090
4091
			$site = isset( $sites[ $this->_slug ] ) ? $sites[ $this->_slug ] : false;
4092
4093
			if ( is_object( $site ) &&
4094
			     is_numeric( $site->id ) &&
4095
			     is_numeric( $site->user_id ) &&
4096
			     is_object( $site->plan )
4097
			) {
4098
				// Load site.
4099
				$this->_site       = clone $site;
4100
				$this->_site->plan = $this->_decrypt_entity( $this->_site->plan );
4101
4102
				// Load relevant user.
4103
				$this->_user = clone $users[ $this->_site->user_id ];
4104
4105
				// Load plans.
4106
				$this->_plans = $plans[ $this->_slug ];
4107
				if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) {
4108
					$this->_sync_plans( true );
0 ignored issues
show
Unused Code introduced by
The call to Freemius::_sync_plans() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
4109
				} else {
4110
					for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
4111
						if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) {
4112
							$this->_plans[ $i ] = $this->_decrypt_entity( $this->_plans[ $i ] );
4113
						} else {
4114
							unset( $this->_plans[ $i ] );
4115
						}
4116
					}
4117
				}
4118
4119
				// Load licenses.
4120
				$this->_licenses = array();
4121
				if ( is_array( $licenses ) &&
4122
				     isset( $licenses[ $this->_slug ] ) &&
4123
				     isset( $licenses[ $this->_slug ][ $this->_user->id ] )
4124
				) {
4125
					$this->_licenses = $licenses[ $this->_slug ][ $this->_user->id ];
4126
				}
4127
4128
				$this->_license = $this->_get_license_by_id( $this->_site->license_id );
4129
4130
				if ( $this->_site->version != $this->get_plugin_version() ) {
4131
					// If stored install version is different than current installed plugin version,
4132
					// then update plugin version event.
4133
					$this->update_plugin_version_event();
4134
				}
4135
			}
4136
4137
			$this->_register_account_hooks();
4138
		}
4139
4140
		/**
4141
		 * @author Vova Feldman (@svovaf)
4142
		 * @since  1.0.1
4143
		 *
4144
		 * @param FS_User    $user
4145
		 * @param FS_Site    $site
4146
		 * @param bool|array $plans
4147
		 */
4148
		private function _set_account( FS_User $user, FS_Site $site, $plans = false ) {
4149
			$site->slug    = $this->_slug;
4150
			$site->user_id = $user->id;
4151
4152
			$this->_site = $site;
4153
			$this->_user = $user;
4154
			if ( false !== $plans ) {
4155
				$this->_plans = $plans;
4156
			}
4157
4158
			$this->send_install_update( array(), true );
4159
4160
			$this->_store_account();
4161
4162
		}
4163
4164
		/**
4165
		 * Set user and site identities.
4166
		 *
4167
		 * @author Vova Feldman (@svovaf)
4168
		 * @since  1.0.9
4169
		 *
4170
		 * @param FS_User $user
4171
		 * @param FS_Site $site
4172
		 * @param bool    $redirect
4173
		 *
4174
		 * @return bool False if account already set.
4175
		 */
4176
		function setup_account( FS_User $user, FS_Site $site, $redirect = 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...
4177
			$this->_user = $user;
4178
			$this->_site = $site;
4179
			$this->_enrich_site_plan( false );
4180
4181
			$this->_set_account( $user, $site );
4182
			$this->_sync_plans();
4183
4184
			if ( $this->is_trial() ) {
4185
				// Store trial plan information.
4186
				$this->_enrich_site_trial_plan( true );
4187
			}
4188
4189
			$this->do_action( 'after_account_connection', $user, $site );
4190
4191
			if ( is_numeric( $site->license_id ) ) {
4192
				$this->_license = $this->_get_license_by_id( $site->license_id );
4193
			}
4194
4195
			if ( $this->is_pending_activation() ) {
4196
				// Remove pending activation sticky notice (if still exist).
4197
				$this->_admin_notices->remove_sticky( 'activation_pending' );
4198
4199
				// Remove plugin from pending activation mode.
4200
				unset( $this->_storage->is_pending_activation );
4201
4202
				if ( ! $this->is_paying() ) {
4203
					$this->_admin_notices->add_sticky(
4204
						sprintf( __fs( 'plugin-x-activation-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
4205
						'activation_complete'
4206
					);
4207
				}
4208
			}
4209
4210
			if ( $this->is_paying() && ! $this->is_premium() ) {
4211
				$this->_admin_notices->add_sticky(
4212
					sprintf(
4213
						__fs( 'activation-with-plan-x-message' ),
4214
						$this->_site->plan->title
4215
					) . ' ' . $this->_get_latest_download_link( sprintf(
4216
						__fs( 'download-latest-x-version' ),
4217
						$this->_site->plan->title
4218
					) ),
4219
					'plan_upgraded',
4220
					__fs( 'yee-haw' ) . '!'
4221
				);
4222
			}
4223
4224
			$plugin_id = fs_request_get( 'plugin_id', false );
4225
4226
			// Store activation time ONLY for plugins (not add-ons).
4227
			if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) {
4228
				$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
0 ignored issues
show
Documentation introduced by
The property activation_timestamp 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...
4229
			}
4230
4231
			if ( is_numeric( $plugin_id ) ) {
4232
				if ( $plugin_id != $this->_plugin->id ) {
4233
					// Add-on was installed - sync license right after install.
4234
					if ( $redirect && fs_redirect( fs_nonce_url( $this->_get_admin_page_url(
4235
							'account',
4236
							array(
4237
								'fs_action' => $this->_slug . '_sync_license',
4238
								'plugin_id' => $plugin_id
4239
							)
4240
						), $this->_slug . '_sync_license' ) )
4241
					) {
4242
						exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method setup_account() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
4243
					}
4244
4245
				}
4246
			} else {
4247
				// Reload the page with the keys.
4248
				if ( $redirect && fs_redirect( $this->get_after_activation_url( 'after_connect_url' ) ) ) {
4249
					exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method setup_account() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
4250
				}
4251
			}
4252
		}
4253
4254
		/**
4255
		 * Install plugin with new user information after approval.
4256
		 *
4257
		 * @author Vova Feldman (@svovaf)
4258
		 * @since  1.0.7
4259
		 */
4260
		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...
4261
			if ( $this->is_registered() ) {
4262
				return;
4263
			}
4264
4265
			if ( fs_request_is_action( $this->_slug . '_activate_new' ) ) {
4266
//				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...
4267
4268
				if ( fs_request_has( 'user_secret_key' ) ) {
4269
					$user             = new FS_User();
4270
					$user->id         = fs_request_get( 'user_id' );
4271
					$user->public_key = fs_request_get( 'user_public_key' );
4272
					$user->secret_key = fs_request_get( 'user_secret_key' );
4273
4274
					$this->_user = $user;
4275
					$user_result = $this->get_api_user_scope()->get();
4276
					$user        = new FS_User( $user_result );
4277
					$this->_user = $user;
4278
4279
					$site             = new FS_Site();
4280
					$site->id         = fs_request_get( 'install_id' );
4281
					$site->public_key = fs_request_get( 'install_public_key' );
4282
					$site->secret_key = fs_request_get( 'install_secret_key' );
4283
4284
					$this->_site = $site;
4285
					$site_result = $this->get_api_site_scope()->get();
4286
					$site        = new FS_Site( $site_result );
4287
					$this->_site = $site;
4288
4289
					$this->setup_account( $this->_user, $this->_site );
4290
				} else if ( fs_request_has( 'pending_activation' ) ) {
4291
					// Install must be activated via email since
4292
					// user with the same email already exist.
4293
					$this->_storage->is_pending_activation = true;
0 ignored issues
show
Documentation introduced by
The property is_pending_activation 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...
4294
					$this->_add_pending_activation_notice( fs_request_get( 'user_email' ) );
4295
4296
					// Reload the page with with pending activation message.
4297
					if ( fs_redirect( $this->get_after_activation_url( 'after_pending_connect_url' ) ) ) {
4298
						exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _install_with_new_user() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
4299
					}
4300
				}
4301
			}
4302
		}
4303
4304
		/**
4305
		 * Install plugin with current logged WP user info.
4306
		 *
4307
		 * @author Vova Feldman (@svovaf)
4308
		 * @since  1.0.7
4309
		 */
4310
		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...
4311
			if ( $this->is_registered() ) {
4312
				return;
4313
			}
4314
4315
			if ( fs_request_is_action( $this->_slug . '_activate_existing' ) && fs_request_is_post() ) {
4316
//				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...
4317
				// Get current logged WP user.
4318
				$current_user = wp_get_current_user();
4319
4320
				// Find the relevant FS user by the email.
4321
				$user = self::_get_user_by_email( $current_user->user_email );
4322
4323
				// We have to set the user before getting user scope API handler.
4324
				$this->_user = $user;
4325
4326
				// Install the plugin.
4327
				$install = $this->get_api_user_scope()->call(
4328
					"/plugins/{$this->get_id()}/installs.json",
4329
					'post',
4330
					$this->get_install_data_for_api( array(
4331
						'uid' => $this->get_anonymous_id(),
4332
					) )
4333
				);
4334
4335
				if ( isset( $install->error ) ) {
4336
					$this->_admin_notices->add(
4337
						sprintf( __fs( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
4338
						__fs( 'contact-us-with-error-message' ) . ' ' . '<b>' . $install->error->message . '</b>',
4339
						__fs( 'oops' ) . '...',
4340
						'error'
4341
					);
4342
4343
					return;
4344
				}
4345
4346
				$site        = new FS_Site( $install );
4347
				$this->_site = $site;
4348
//				$this->_enrich_site_plan( false );
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...
4349
4350
//				$this->_set_account( $user, $site );
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% 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...
4351
//				$this->_sync_plans();
4352
4353
				$this->setup_account( $this->_user, $this->_site );
4354
			}
4355
		}
4356
4357
		/**
4358
		 * Tries to activate add-on account based on parent plugin info.
4359
		 *
4360
		 * @author Vova Feldman (@svovaf)
4361
		 * @since  1.0.6
4362
		 *
4363
		 * @param Freemius $parent_fs
4364
		 */
4365
		private function _activate_addon_account( Freemius $parent_fs ) {
4366
			if ( $this->is_registered() ) {
4367
				// Already activated.
4368
				return;
4369
			}
4370
4371
			// Activate add-on with parent plugin credentials.
4372
			$addon_install = $parent_fs->get_api_site_scope()->call(
4373
				"/addons/{$this->_plugin->id}/installs.json",
4374
				'post',
4375
				$this->get_install_data_for_api( array(
4376
					'uid' => $this->get_anonymous_id(),
4377
				) )
4378
			);
4379
4380
			if ( isset( $addon_install->error ) ) {
4381
				$this->_admin_notices->add(
4382
					sprintf( __fs( 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
4383
					__fs( 'contact-us-with-error-message' ) . ' ' . '<b>' . $addon_install->error->message . '</b>',
4384
					__fs( 'oops' ) . '...',
4385
					'error'
4386
				);
4387
4388
				return;
4389
			}
4390
4391
			// First of all, set site info - otherwise we won't
4392
			// be able to invoke API calls.
4393
			$this->_site = new FS_Site( $addon_install );
4394
4395
			// Sync add-on plans.
4396
			$this->_sync_plans();
4397
4398
			// Get site's current plan.
4399
			$this->_site->plan = $this->_get_plan_by_id( $this->_site->plan->id );
4400
4401
			// Get user information based on parent's plugin.
4402
			$user = $parent_fs->get_user();
4403
4404
			$this->_set_account( $user, $this->_site );
4405
4406
			// Sync licenses.
4407
			$this->_sync_licenses();
4408
4409
			// Try to activate premium license.
4410
			$this->_activate_license( true );
4411
		}
4412
4413
		#endregion ------------------------------------------------------------------
4414
4415
		#region Admin Menu Items ------------------------------------------------------------------
4416
4417
		private $_menu_items = array();
4418
4419
		/**
4420
		 * @author Vova Feldman (@svovaf)
4421
		 * @since  1.0.7
4422
		 *
4423
		 * @return string
4424
		 */
4425
		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...
4426
			return $this->_menu->get_slug();
4427
		}
4428
4429
		/**
4430
		 * @author Vova Feldman (@svovaf)
4431
		 * @since  1.0.9
4432
		 */
4433
		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...
4434
			if ( ! $this->is_on() ) {
4435
				return;
4436
			}
4437
4438
			if ( ! $this->has_api_connectivity() && ! $this->enable_anonymous() ) {
4439
				$this->_menu->remove_menu_item();
4440
			} else {
4441
				$this->add_submenu_items();
4442
				$this->add_menu_action();
4443
			}
4444
		}
4445
4446
		/**
4447
		 * Admin dashboard menu items modifications.
4448
		 *
4449
		 * NOTE: admin_menu action executed before admin_init.
4450
		 *
4451
		 * @author Vova Feldman (@svovaf)
4452
		 * @since  1.0.7
4453
		 *
4454
		 */
4455
		private function add_menu_action() {
4456
			if ( $this->is_activation_mode() ) {
4457
				$this->override_plugin_menu_with_activation();
4458
			} else {
4459
				// If not registered try to install user.
4460
				if ( ! $this->is_registered() &&
4461
				     fs_request_is_action( $this->_slug . '_activate_new' )
4462
				) {
4463
					$this->_install_with_new_user();
4464
				}
4465
			}
4466
		}
4467
4468
		/**
4469
		 * @author Vova Feldman (@svovaf)
4470
		 * @since  1.0.1
4471
		 *
4472
		 * @return string
4473
		 */
4474
		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...
4475
			$this->_logger->entrance();
4476
4477
			$page = strtolower( isset( $_REQUEST['page'] ) ? $_REQUEST['page'] : '' );
4478
4479
			$this->_logger->log( 'page = ' . $page );
4480
4481
			foreach ( $this->_menu_items as $priority => $items ) {
4482
				foreach ( $items as $item ) {
4483
					if ( isset( $item['url'] ) ) {
4484
						if ( $page === $item['menu_slug'] ) {
4485
							$this->_logger->log( 'Redirecting to ' . $item['url'] );
4486
4487
							fs_redirect( $item['url'] );
4488
						}
4489
					}
4490
				}
4491
			}
4492
		}
4493
4494
		/**
4495
		 * Remove plugin's all admin menu items & pages, and replace with activation page.
4496
		 *
4497
		 * @author Vova Feldman (@svovaf)
4498
		 * @since  1.0.1
4499
		 */
4500
		private function override_plugin_menu_with_activation() {
4501
			$this->_logger->entrance();
4502
4503
			$hook = false;
4504
4505
			if ( $this->_menu->is_top_level() ) {
4506
				$menu = $this->_menu->remove_menu_item();
4507
4508
				if ( false !== $menu ) {
4509
					// Override menu action.
4510
					$hook = add_menu_page(
4511
						$menu['menu'][3],
4512
						$menu['menu'][0],
4513
						'manage_options',
4514
						$this->_menu->get_slug(),
4515
						array( &$this, '_connect_page_render' ),
4516
						$menu['menu'][6],
4517
						$menu['position']
4518
					);
4519
				}
4520
			} else {
4521
				if ( $this->_menu->has_custom_parent() ) {
4522
					$menus = array( $this->_menu->get_parent_slug() );
4523
4524
					if ( $this->_menu->is_override_exact() ) {
4525
						// Make sure the current page is matching the activation page.
4526
						if ( fs_canonize_url( $_SERVER['REQUEST_URI'] ) !== fs_canonize_url( $this->get_activation_url(), true ) ) {
4527
							// DO NOT OVERRIDE PAGE.
4528
							return;
4529
						}
4530
					}
4531
				} else {
4532
					$menus = array(
4533
						'tools.php',
4534
						'options-general.php',
4535
					);
4536
				}
4537
4538
				foreach ( $menus as $parent_slug ) {
4539
					$hook = $this->_menu->override_submenu_action(
4540
						$parent_slug,
4541
						$this->_menu->get_raw_slug(),
4542
						array( &$this, '_connect_page_render' )
4543
					);
4544
4545
					if ( false !== $hook ) {
4546
						// Found plugin's submenu item.
4547
						break;
4548
					}
4549
				}
4550
			}
4551
4552
			if ( $this->_menu->is_activation_page() ) {
4553
				// Clean admin page from distracting content.
4554
				$this->_clean_admin_content_section();
4555
			}
4556
4557
			if ( false !== $hook ) {
4558
				if ( fs_request_is_action( $this->_slug . '_activate_existing' ) ) {
4559
					add_action( "load-$hook", array( &$this, '_install_with_current_user' ) );
4560
				} else if ( fs_request_is_action( $this->_slug . '_activate_new' ) ) {
4561
					add_action( "load-$hook", array( &$this, '_install_with_new_user' ) );
4562
				}
4563
			}
4564
		}
4565
4566
4567
		/**
4568
		 * Add default Freemius menu items.
4569
		 *
4570
		 * @author Vova Feldman (@svovaf)
4571
		 * @since  1.0.0
4572
		 */
4573
		private function add_submenu_items() {
4574
			$this->_logger->entrance();
4575
4576
			$this->do_action( 'before_admin_menu_init' );
4577
4578
			if ( ! $this->is_addon() ) {
4579
				if ( $this->is_registered() || $this->is_anonymous() ) {
4580
					if ( $this->is_registered() ) {
4581
						// Add user account page.
4582
						$this->add_submenu_item(
4583
							__fs( 'account' ),
4584
							array( &$this, '_account_page_render' ),
4585
							$this->get_plugin_name() . ' &ndash; ' . __fs( 'account' ),
4586
							'manage_options',
4587
							'account',
4588
							array( &$this, '_account_page_load' ),
4589
							10,
4590
							$this->_menu->is_submenu_item_visible( 'account' )
4591
						);
4592
					}
4593
4594
					// Add contact page.
4595
					$this->add_submenu_item(
4596
						__fs( 'contact-us' ),
4597
						array( &$this, '_contact_page_render' ),
4598
						$this->get_plugin_name() . ' &ndash; ' . __fs( 'contact-us' ),
4599
						'manage_options',
4600
						'contact',
4601
						array( &$this, '_clean_admin_content_section' ),
4602
						10,
4603
						$this->_menu->is_submenu_item_visible( 'contact' )
4604
					);
4605
4606
					if ( $this->_has_addons() ) {
4607
						$this->add_submenu_item(
4608
							__fs( 'add-ons' ),
4609
							array( &$this, '_addons_page_render' ),
4610
							$this->get_plugin_name() . ' &ndash; ' . __fs( 'add-ons' ),
4611
							'manage_options',
4612
							'addons',
4613
							array( &$this, '_addons_page_load' ),
4614
							WP_FS__LOWEST_PRIORITY - 1,
4615
							$this->_menu->is_submenu_item_visible( 'addons' )
4616
						);
4617
					}
4618
4619
					// Add upgrade/pricing page.
4620
					$this->add_submenu_item(
4621
						( $this->is_paying() ? __fs( 'pricing' ) : __fs( 'upgrade' ) . '&nbsp;&nbsp;&#x27a4;' ),
4622
						array( &$this, '_pricing_page_render' ),
4623
						$this->get_plugin_name() . ' &ndash; ' . __fs( 'pricing' ),
4624
						'manage_options',
4625
						'pricing',
4626
						array( &$this, '_clean_admin_content_section' ),
4627
						WP_FS__LOWEST_PRIORITY,
4628
						// If user don't have paid plans, add pricing page
4629
						// to support add-ons checkout but don't add the submenu item.
4630
						$this->_menu->is_submenu_item_visible( 'pricing' ) && ( $this->has_paid_plan() || ( isset( $_GET['page'] ) && $this->_menu->get_slug( 'pricing' ) == $_GET['page'] ) )
4631
					);
4632
				}
4633
			}
4634
4635
			ksort( $this->_menu_items );
4636
4637
			foreach ( $this->_menu_items as $priority => $items ) {
4638
				foreach ( $items as $item ) {
4639
					if ( ! isset( $item['url'] ) ) {
4640
						$hook = add_submenu_page(
4641
							$item['show_submenu'] ?
4642
								( $this->is_addon() ?
4643
									$this->get_parent_instance()->_menu->get_original_menu_slug() :
4644
									$this->_menu->get_original_menu_slug() ) :
4645
								null,
4646
							$item['page_title'],
4647
							$item['menu_title'],
4648
							$item['capability'],
4649
							$item['menu_slug'],
4650
							$item['render_function']
4651
						);
4652
4653
						if ( false !== $item['before_render_function'] ) {
4654
							add_action( "load-$hook", $item['before_render_function'] );
4655
						}
4656
					} else {
4657
						add_submenu_page(
4658
							$this->is_addon() ?
4659
								$this->get_parent_instance()->_menu->get_original_menu_slug() :
4660
								$this->_menu->get_original_menu_slug(),
4661
							$item['page_title'],
4662
							$item['menu_title'],
4663
							$item['capability'],
4664
							$item['menu_slug'],
4665
							array( $this, '' )
4666
						);
4667
					}
4668
				}
4669
			}
4670
		}
4671
4672
		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...
4673
			if ( ! $this->is_on() ) {
4674
				return;
4675
			}
4676
4677
			if ( $this->is_registered() ) {
4678
				if ( $this->_menu->is_submenu_item_visible( 'support' ) ) {
4679
					$this->add_submenu_link_item(
4680
						__fs( 'support-forum' ),
4681
						'https://wordpress.org/support/plugin/' . $this->_slug,
4682
						'wp-support-forum',
4683
						'read',
4684
						50
4685
					);
4686
				}
4687
			}
4688
		}
4689
4690
		/**
4691
		 * @author Vova Feldman (@svovaf)
4692
		 * @since  1.0.1
4693
		 *
4694
		 * @param string        $menu_title
4695
		 * @param callable      $render_function
4696
		 * @param bool|string   $page_title
4697
		 * @param string        $capability
4698
		 * @param bool|string   $menu_slug
4699
		 * @param bool|callable $before_render_function
4700
		 * @param int           $priority
4701
		 * @param bool          $show_submenu
4702
		 */
4703
		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...
4704
			$menu_title,
4705
			$render_function,
4706
			$page_title = false,
4707
			$capability = 'manage_options',
4708
			$menu_slug = false,
4709
			$before_render_function = false,
4710
			$priority = 10,
4711
			$show_submenu = true
4712
		) {
4713
			$this->_logger->entrance( 'Title = ' . $menu_title );
4714
4715
			if ( $this->is_addon() ) {
4716
				$parent_fs = $this->get_parent_instance();
4717
4718
				if ( is_object( $parent_fs ) ) {
4719
					$parent_fs->add_submenu_item(
4720
						$menu_title,
4721
						$render_function,
4722
						$page_title,
4723
						$capability,
4724
						$menu_slug,
4725
						$before_render_function,
4726
						$priority,
4727
						$show_submenu
4728
					);
4729
4730
					return;
4731
				}
4732
			}
4733
4734
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
4735
				$this->_menu_items[ $priority ] = array();
4736
			}
4737
4738
			$this->_menu_items[ $priority ][] = array(
4739
				'page_title'             => is_string( $page_title ) ? $page_title : $menu_title,
4740
				'menu_title'             => $menu_title,
4741
				'capability'             => $capability,
4742
				'menu_slug'              => $this->_menu->get_slug( is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ) ),
4743
				'render_function'        => $render_function,
4744
				'before_render_function' => $before_render_function,
4745
				'show_submenu'           => $show_submenu,
4746
			);
4747
		}
4748
4749
		/**
4750
		 * @author Vova Feldman (@svovaf)
4751
		 * @since  1.0.1
4752
		 *
4753
		 * @param string $menu_title
4754
		 * @param string $url
4755
		 * @param bool   $menu_slug
4756
		 * @param string $capability
4757
		 * @param int    $priority
4758
		 *
4759
		 */
4760
		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...
4761
			$menu_title,
4762
			$url,
4763
			$menu_slug = false,
4764
			$capability = 'read',
4765
			$priority = 10
4766
		) {
4767
			$this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url );
4768
4769
			if ( $this->is_addon() ) {
4770
				$parent_fs = $this->get_parent_instance();
4771
4772
				if ( is_object( $parent_fs ) ) {
4773
					$parent_fs->add_submenu_link_item(
4774
						$menu_title,
4775
						$url,
4776
						$menu_slug,
4777
						$capability,
4778
						$priority
4779
					);
4780
4781
					return;
4782
				}
4783
			}
4784
4785
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
4786
				$this->_menu_items[ $priority ] = array();
4787
			}
4788
4789
			$this->_menu_items[ $priority ][] = array(
4790
				'menu_title'             => $menu_title,
4791
				'capability'             => $capability,
4792
				'menu_slug'              => $this->_menu->get_slug( is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ) ),
4793
				'url'                    => $url,
4794
				'page_title'             => $menu_title,
4795
				'render_function'        => 'fs_dummy',
4796
				'before_render_function' => '',
4797
			);
4798
		}
4799
4800
		#endregion ------------------------------------------------------------------
4801
4802
		/* Actions / Hooks / Filters
4803
		------------------------------------------------------------------------------------------------------------------*/
4804
		/**
4805
		 * Do action, specific for the current context plugin.
4806
		 *
4807
		 * @author Vova Feldman (@svovaf)
4808
		 * @since  1.0.1
4809
		 *
4810
		 * @param string $tag     The name of the action to be executed.
4811
		 * @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...
4812
		 *                        functions hooked to the action. Default empty.
4813
		 *
4814
		 * @uses   do_action()
4815
		 */
4816
		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...
4817
			$this->_logger->entrance( $tag );
4818
4819
			$args = func_get_args();
4820
4821
			call_user_func_array( 'do_action', array_merge(
4822
					array( 'fs_' . $tag . '_' . $this->_slug ),
4823
					array_slice( $args, 1 ) )
4824
			);
4825
		}
4826
4827
		/**
4828
		 * Add action, specific for the current context plugin.
4829
		 *
4830
		 * @author Vova Feldman (@svovaf)
4831
		 * @since  1.0.1
4832
		 *
4833
		 * @param string   $tag
4834
		 * @param callable $function_to_add
4835
		 * @param int      $priority
4836
		 * @param int      $accepted_args
4837
		 *
4838
		 * @uses   add_action()
4839
		 */
4840
		function add_action( $tag, $function_to_add, $priority = 10, $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...
4841
			$this->_logger->entrance( $tag );
4842
4843
			add_action( 'fs_' . $tag . '_' . $this->_slug, $function_to_add, $priority, $accepted_args );
4844
		}
4845
4846
		/**
4847
		 * Apply filter, specific for the current context plugin.
4848
		 *
4849
		 * @author Vova Feldman (@svovaf)
4850
		 * @since  1.0.9
4851
		 *
4852
		 * @param string $tag   The name of the filter hook.
4853
		 * @param mixed  $value The value on which the filters hooked to `$tag` are applied on.
4854
		 *
4855
		 * @return mixed The filtered value after all hooked functions are applied to it.
4856
		 *
4857
		 * @uses   apply_filters()
4858
		 */
4859
		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...
4860
			$this->_logger->entrance( $tag );
4861
4862
			$args = func_get_args();
4863
4864
			return call_user_func_array( 'apply_filters', array_merge(
4865
					array( 'fs_' . $tag . '_' . $this->_slug ),
4866
					array_slice( $args, 1 ) )
4867
			);
4868
		}
4869
4870
		/**
4871
		 * Add filter, specific for the current context plugin.
4872
		 *
4873
		 * @author Vova Feldman (@svovaf)
4874
		 * @since  1.0.9
4875
		 *
4876
		 * @param string   $tag
4877
		 * @param callable $function_to_add
4878
		 * @param int      $priority
4879
		 * @param int      $accepted_args
4880
		 *
4881
		 * @uses   add_filter()
4882
		 */
4883
		function add_filter( $tag, $function_to_add, $priority = 10, $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...
4884
			$this->_logger->entrance( $tag );
4885
4886
			add_filter( 'fs_' . $tag . '_' . $this->_slug, $function_to_add, $priority, $accepted_args );
4887
		}
4888
4889
		/* Activation
4890
		------------------------------------------------------------------------------------------------------------------*/
4891
		/**
4892
		 * Render activation/sign-up page.
4893
		 *
4894
		 * @author Vova Feldman (@svovaf)
4895
		 * @since  1.0.1
4896
		 */
4897
		function _activation_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...
4898
			$this->_logger->entrance();
4899
4900
			$vars = array( 'slug' => $this->_slug );
4901
			fs_require_once_template( 'activation.php', $vars );
4902
		}
4903
4904
		/* Account Page
4905
		------------------------------------------------------------------------------------------------------------------*/
4906
		/**
4907
		 * Update site information.
4908
		 *
4909
		 * @author Vova Feldman (@svovaf)
4910
		 * @since  1.0.1
4911
		 *
4912
		 * @param bool $store Flush to Database if true.
4913
		 */
4914
		private function _store_site( $store = true ) {
4915
			$this->_logger->entrance();
4916
4917
			$encrypted_site       = clone $this->_site;
4918
			$encrypted_site->plan = $this->_encrypt_entity( $this->_site->plan );
4919
4920
			$sites                 = self::get_all_sites();
4921
			$sites[ $this->_slug ] = $encrypted_site;
4922
			self::$_accounts->set_option( 'sites', $sites, $store );
4923
		}
4924
4925
		/**
4926
		 * Update plugin's plans information.
4927
		 *
4928
		 * @author Vova Feldman (@svovaf)
4929
		 * @since  1.0.2
4930
		 *
4931
		 * @param bool $store Flush to Database if true.
4932
		 */
4933
		private function _store_plans( $store = true ) {
4934
			$this->_logger->entrance();
4935
4936
			$plans = self::get_all_plans();
4937
4938
			// Copy plans.
4939
			$encrypted_plans = array();
4940
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
4941
				$encrypted_plans[] = $this->_encrypt_entity( $this->_plans[ $i ] );
4942
			}
4943
4944
			$plans[ $this->_slug ] = $encrypted_plans;
4945
			self::$_accounts->set_option( 'plans', $plans, $store );
4946
		}
4947
4948
		/**
4949
		 * Update user's plugin licenses.
4950
		 *
4951
		 * @author Vova Feldman (@svovaf)
4952
		 * @since  1.0.5
4953
		 *
4954
		 * @param bool                $store
4955
		 * @param string|bool         $plugin_slug
4956
		 * @param FS_Plugin_License[] $licenses
4957
		 */
4958
		private function _store_licenses( $store = true, $plugin_slug = false, $licenses = array() ) {
4959
			$this->_logger->entrance();
4960
4961
			$all_licenses = self::get_all_licenses();
4962
4963
			if ( ! is_string( $plugin_slug ) ) {
4964
				$plugin_slug = $this->_slug;
4965
				$licenses    = $this->_licenses;
4966
			}
4967
4968
			if ( ! isset( $all_licenses[ $plugin_slug ] ) ) {
4969
				$all_licenses[ $plugin_slug ] = array();
4970
			}
4971
4972
			$all_licenses[ $plugin_slug ][ $this->_user->id ] = $licenses;
4973
4974
			self::$_accounts->set_option( 'licenses', $all_licenses, $store );
4975
		}
4976
4977
		/**
4978
		 * Update user information.
4979
		 *
4980
		 * @author Vova Feldman (@svovaf)
4981
		 * @since  1.0.1
4982
		 *
4983
		 * @param bool $store Flush to Database if true.
4984
		 */
4985
		private function _store_user( $store = true ) {
4986
			$this->_logger->entrance();
4987
4988
			$users                     = self::get_all_users();
4989
			$users[ $this->_user->id ] = $this->_user;
4990
			self::$_accounts->set_option( 'users', $users, $store );
4991
		}
4992
4993
		/**
4994
		 * Update new updates information.
4995
		 *
4996
		 * @author Vova Feldman (@svovaf)
4997
		 * @since  1.0.4
4998
		 *
4999
		 * @param FS_Plugin_Tag|null $update
5000
		 * @param bool               $store Flush to Database if true.
5001
		 * @param bool|number        $plugin_id
5002
		 */
5003
		private function _store_update( $update, $store = true, $plugin_id = false ) {
5004
			$this->_logger->entrance();
5005
5006
			if ( $update instanceof FS_Plugin_Tag ) {
5007
				$update->updated = time();
5008
			}
5009
5010
			if ( ! is_numeric( $plugin_id ) ) {
5011
				$plugin_id = $this->_plugin->id;
5012
			}
5013
5014
			$updates               = self::get_all_updates();
5015
			$updates[ $plugin_id ] = $update;
5016
			self::$_accounts->set_option( 'updates', $updates, $store );
5017
		}
5018
5019
		/**
5020
		 * Update new updates information.
5021
		 *
5022
		 * @author   Vova Feldman (@svovaf)
5023
		 * @since    1.0.6
5024
		 *
5025
		 * @param FS_Plugin[] $plugin_addons
5026
		 * @param bool        $store Flush to Database if true.
5027
		 */
5028
		private function _store_addons( $plugin_addons, $store = true ) {
5029
			$this->_logger->entrance();
5030
5031
			$addons                       = self::get_all_addons();
5032
			$addons[ $this->_plugin->id ] = $plugin_addons;
5033
			self::$_accounts->set_option( 'addons', $addons, $store );
5034
		}
5035
5036
		/**
5037
		 * Delete plugin's associated add-ons.
5038
		 *
5039
		 * @author   Vova Feldman (@svovaf)
5040
		 * @since    1.0.8
5041
		 *
5042
		 * @param bool $store
5043
		 *
5044
		 * @return bool
5045
		 */
5046
		private function _delete_account_addons( $store = true ) {
5047
			$all_addons = self::get_all_account_addons();
5048
5049
			if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) {
5050
				return false;
5051
			}
5052
5053
			unset( $all_addons[ $this->_plugin->id ] );
5054
5055
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
5056
5057
			return true;
5058
		}
5059
5060
		/**
5061
		 * Update account add-ons list.
5062
		 *
5063
		 * @author   Vova Feldman (@svovaf)
5064
		 * @since    1.0.6
5065
		 *
5066
		 * @param FS_Plugin[] $addons
5067
		 * @param bool        $store Flush to Database if true.
5068
		 */
5069
		private function _store_account_addons( $addons, $store = true ) {
5070
			$this->_logger->entrance();
5071
5072
			$all_addons                       = self::get_all_account_addons();
5073
			$all_addons[ $this->_plugin->id ] = $addons;
5074
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
5075
		}
5076
5077
		/**
5078
		 * Store account params in the Database.
5079
		 *
5080
		 * @author Vova Feldman (@svovaf)
5081
		 * @since  1.0.1
5082
		 */
5083
		private function _store_account() {
5084
			$this->_logger->entrance();
5085
5086
			$this->_store_site( false );
5087
			$this->_store_user( false );
5088
			$this->_store_plans( false );
5089
			$this->_store_licenses( false );
5090
5091
			self::$_accounts->store();
5092
		}
5093
5094
		/**
5095
		 * Sync user's information.
5096
		 *
5097
		 * @author Vova Feldman (@svovaf)
5098
		 * @since  1.0.3
5099
		 * @uses   FS_Api
5100
		 */
5101
		private function _handle_account_user_sync() {
5102
			$this->_logger->entrance();
5103
5104
			$api = $this->get_api_user_scope();
5105
5106
			// Get user's information.
5107
			$user = $api->get( '/', true );
5108
5109
			if ( isset( $user->id ) ) {
5110
				$this->_user->first = $user->first;
5111
				$this->_user->last  = $user->last;
5112
				$this->_user->email = $user->email;
5113
5114
				if ( ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified ) && $user->is_verified ) {
5115
					$this->_user->is_verified = $user->is_verified;
5116
5117
					$this->do_action( 'account_email_verified', $user->email );
5118
5119
					$this->_admin_notices->add(
5120
						__fs( 'email-verified-message' ),
5121
						__fs( 'right-on' ) . '!'
5122
					);
5123
				}
5124
5125
				// Flush user details to DB.
5126
				$this->_store_user();
5127
5128
				$this->do_action( 'after_account_user_sync', $user );
5129
			}
5130
		}
5131
5132
		/**
5133
		 * @author Vova Feldman (@svovaf)
5134
		 * @since  1.0.5
5135
		 * @uses   FS_Api
5136
		 *
5137
		 * @param bool $flush
5138
		 *
5139
		 * @return object|\FS_Site
5140
		 */
5141
		private function _fetch_site( $flush = false ) {
5142
			$this->_logger->entrance();
5143
			$api = $this->get_api_site_scope();
5144
5145
			$site = $api->get( '/', $flush );
5146
5147
			if ( ! isset( $site->error ) ) {
5148
				$site          = new FS_Site( $site );
5149
				$site->slug    = $this->_slug;
5150
				$site->version = $this->get_plugin_version();
5151
			}
5152
5153
			return $site;
5154
		}
5155
5156
		/**
5157
		 * @param bool $store
5158
		 *
5159
		 * @return FS_Plugin_Plan|object|false
5160
		 */
5161
		private function _enrich_site_plan( $store = true ) {
5162
			// Try to load plan from local cache.
5163
			$plan = $this->_get_plan_by_id( $this->_site->plan->id );
5164
5165
			if ( false === $plan ) {
5166
				$plan = $this->_fetch_site_plan();
5167
			}
5168
5169
			if ( $plan instanceof FS_Plugin_Plan ) {
5170
				$this->_update_plan( $plan, $store );
5171
			}
5172
5173
			return $plan;
5174
		}
5175
5176
		/**
5177
		 * @author Vova Feldman (@svovaf)
5178
		 * @since  1.0.9
5179
		 * @uses   FS_Api
5180
		 *
5181
		 * @param bool $store
5182
		 *
5183
		 * @return FS_Plugin_Plan|object|false
5184
		 */
5185
		private function _enrich_site_trial_plan( $store = true ) {
5186
			// Try to load plan from local cache.
5187
			$trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id );
5188
5189
			if ( false === $trial_plan ) {
5190
				$trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id );
5191
			}
5192
5193
			if ( $trial_plan instanceof FS_Plugin_Plan ) {
5194
				$this->_storage->store( 'trial_plan', $trial_plan, $store );
5195
			}
5196
5197
			return $trial_plan;
5198
		}
5199
5200
		/**
5201
		 * @author Vova Feldman (@svovaf)
5202
		 * @since  1.0.9
5203
		 * @uses   FS_Api
5204
		 *
5205
		 * @param number|bool $license_id
5206
		 *
5207
		 * @return FS_Subscription|object|bool
5208
		 */
5209
		private function _fetch_site_license_subscription( $license_id = false ) {
5210
			$this->_logger->entrance();
5211
			$api = $this->get_api_site_scope();
5212
5213
			if ( ! is_numeric( $license_id ) ) {
5214
				$license_id = $this->_license->id;
5215
			}
5216
5217
			$result = $api->get( "/licenses/{$license_id}/subscriptions.json", true );
5218
5219
			return ! isset( $result->error ) ?
5220
				( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ?
5221
					new FS_Subscription( $result->subscriptions[0] ) :
5222
					false
5223
				) :
5224
				$result;
5225
		}
5226
5227
		/**
5228
		 * @author Vova Feldman (@svovaf)
5229
		 * @since  1.0.4
5230
		 * @uses   FS_Api
5231
		 *
5232
		 * @param number|bool $plan_id
5233
		 *
5234
		 * @return FS_Plugin_Plan|object
5235
		 */
5236
		private function _fetch_site_plan( $plan_id = false ) {
5237
			$this->_logger->entrance();
5238
			$api = $this->get_api_site_scope();
5239
5240
			if ( ! is_numeric( $plan_id ) ) {
5241
				$plan_id = $this->_site->plan->id;
5242
			}
5243
5244
			$plan = $api->get( "/plans/{$plan_id}.json", true );
5245
5246
			return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan;
5247
		}
5248
5249
		/**
5250
		 * @author Vova Feldman (@svovaf)
5251
		 * @since  1.0.5
5252
		 * @uses   FS_Api
5253
		 *
5254
		 * @return FS_Plugin_Plan[]|object
5255
		 */
5256
		private function _fetch_plugin_plans() {
5257
			$this->_logger->entrance();
5258
			$api = $this->get_api_site_scope();
5259
5260
			$result = $api->get( '/plans.json', true );
5261
5262
			if ( ! $this->is_api_error( $result ) ) {
5263
				for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) {
5264
					$result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] );
5265
				}
5266
5267
				$result = $result->plans;
5268
			}
5269
5270
			return $result;
5271
		}
5272
5273
		/**
5274
		 * @author Vova Feldman (@svovaf)
5275
		 * @since  1.0.5
5276
		 * @uses   FS_Api
5277
		 *
5278
		 * @param number|bool $plugin_id
5279
		 *
5280
		 * @return FS_Plugin_License[]|object
5281
		 */
5282
		private function _fetch_licenses( $plugin_id = false ) {
5283
			$this->_logger->entrance();
5284
5285
			$api = $this->get_api_user_scope();
5286
5287
			if ( ! is_numeric( $plugin_id ) ) {
5288
				$plugin_id = $this->_plugin->id;
5289
			}
5290
5291
			$result = $api->get( "/plugins/{$plugin_id}/licenses.json", true );
5292
5293
			if ( ! isset( $result->error ) ) {
5294
				for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) {
5295
					$result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] );
5296
				}
5297
5298
				$result = $result->licenses;
5299
			}
5300
5301
			return $result;
5302
		}
5303
5304
		/**
5305
		 * @author Vova Feldman (@svovaf)
5306
		 * @since  1.0.4
5307
		 *
5308
		 * @param FS_Plugin_Plan $plan
5309
		 * @param bool           $store
5310
		 */
5311
		private function _update_plan( $plan, $store = false ) {
5312
			$this->_logger->entrance();
5313
5314
			$this->_site->plan = $plan;
5315
			$this->_store_site( $store );
5316
		}
5317
5318
		/**
5319
		 * @author Vova Feldman (@svovaf)
5320
		 * @since  1.0.5
5321
		 *
5322
		 * @param FS_Plugin_License[] $licenses
5323
		 * @param string|bool         $plugin_slug
5324
		 */
5325
		private function _update_licenses( $licenses, $plugin_slug = false ) {
5326
			$this->_logger->entrance();
5327
5328
			if ( is_array( $licenses ) ) {
5329
				for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) {
5330
					$licenses[ $i ]->updated = time();
5331
				}
5332
			}
5333
5334
			if ( ! is_string( $plugin_slug ) ) {
5335
				$this->_licenses = $licenses;
5336
			}
5337
5338
			$this->_store_licenses( true, $plugin_slug, $licenses );
5339
		}
5340
5341
		/**
5342
		 * @author Vova Feldman (@svovaf)
5343
		 * @since  1.0.4
5344
		 *
5345
		 * @param bool|number $plugin_id
5346
		 *
5347
		 * @return object|false New plugin tag info if exist.
5348
		 */
5349
		private function _fetch_newer_version( $plugin_id = false ) {
5350
			$latest_tag = $this->_fetch_latest_version( $plugin_id );
5351
5352
			if ( ! is_object( $latest_tag ) ) {
5353
				return false;
5354
			}
5355
5356
			// Check if version is actually newer.
5357
			$has_new_version =
5358
				// If it's an non-installed add-on then always return latest.
5359
				( $this->_is_addon_id( $plugin_id ) && ! $this->is_addon_activated( $plugin_id ) ) ||
5360
				// Compare versions.
5361
				version_compare( $this->get_plugin_version(), $latest_tag->version, '<' );
5362
5363
			$this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' );
5364
5365
			return $has_new_version ? $latest_tag : false;
5366
		}
5367
5368
		/**
5369
		 * @author Vova Feldman (@svovaf)
5370
		 * @since  1.0.5
5371
		 *
5372
		 * @param bool|number $plugin_id
5373
		 *
5374
		 * @return bool|FS_Plugin_Tag
5375
		 */
5376
		function get_update( $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...
5377
			$this->_logger->entrance();
5378
5379
			if ( ! is_numeric( $plugin_id ) ) {
5380
				$plugin_id = $this->_plugin->id;
5381
			}
5382
5383
			$this->_check_updates( true, $plugin_id );
5384
			$updates = $this->get_all_updates();
5385
5386
			return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
5387
		}
5388
5389
		/**
5390
		 * Check if site assigned with active license.
5391
		 *
5392
		 * @author Vova Feldman (@svovaf)
5393
		 * @since  1.0.6
5394
		 */
5395
		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...
5396
			return (
5397
				is_object( $this->_license ) &&
5398
				is_numeric( $this->_license->id ) &&
5399
				! $this->_license->is_expired()
5400
			);
5401
		}
5402
5403
		/**
5404
		 * Check if site assigned with license with enabled features.
5405
		 *
5406
		 * @author Vova Feldman (@svovaf)
5407
		 * @since  1.0.6
5408
		 *
5409
		 * @return bool
5410
		 */
5411
		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...
5412
			return (
5413
				is_object( $this->_license ) &&
5414
				is_numeric( $this->_license->id ) &&
5415
				$this->_license->is_features_enabled()
5416
			);
5417
		}
5418
5419
		/**
5420
		 * Sync site's plan.
5421
		 *
5422
		 * @author Vova Feldman (@svovaf)
5423
		 * @since  1.0.3
5424
		 *
5425
		 * @uses   FS_Api
5426
		 *
5427
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
5428
		 *                         the admin.
5429
		 */
5430
		private function _sync_license( $background = false ) {
5431
			$this->_logger->entrance();
5432
5433
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
5434
5435
			$is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() );
5436
5437
			if ( $is_addon_sync ) {
5438
				$this->_sync_addon_license( $plugin_id, $background );
5439
			} else {
5440
				$this->_sync_plugin_license( $background );
5441
			}
5442
5443
			$this->do_action( 'after_account_plan_sync', $this->_site->plan->name );
5444
		}
5445
5446
		/**
5447
		 * Sync plugin's add-on license.
5448
		 *
5449
		 * @author Vova Feldman (@svovaf)
5450
		 * @since  1.0.6
5451
		 * @uses   FS_Api
5452
		 *
5453
		 * @param number $addon_id
5454
		 * @param bool   $background
5455
		 */
5456
		private function _sync_addon_license( $addon_id, $background ) {
5457
			$this->_logger->entrance();
5458
5459
			if ( $this->is_addon_activated( $addon_id ) ) {
5460
				// If already installed, use add-on sync.
5461
				$fs_addon = self::get_instance_by_id( $addon_id );
5462
				$fs_addon->_sync_license( $background );
5463
5464
				return;
5465
			}
5466
5467
			// Validate add-on exists.
5468
			$addon = $this->get_addon( $addon_id );
5469
5470
			if ( ! is_object( $addon ) ) {
5471
				return;
5472
			}
5473
5474
			// Add add-on into account add-ons.
5475
			$account_addons = $this->get_account_addons();
5476
			if ( ! is_array( $account_addons ) ) {
5477
				$account_addons = array();
5478
			}
5479
			$account_addons[] = $addon->id;
5480
			$account_addons   = array_unique( $account_addons );
5481
			$this->_store_account_addons( $account_addons );
5482
5483
			// Load add-on licenses.
5484
			$licenses = $this->_fetch_licenses( $addon->id );
5485
5486
			// Sync add-on licenses.
5487
			if ( ! isset( $licenses->error ) ) {
5488
				$this->_update_licenses( $licenses, $addon->slug );
5489
5490
				if ( ! $this->is_addon_installed( $addon->slug ) && FS_License_Manager::has_premium_license( $licenses ) ) {
5491
					$plans_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$addon_id}/plans.json" );
5492
5493
					if ( ! isset( $plans_result->error ) ) {
5494
						$plans = array();
5495
						foreach ( $plans_result->plans as $plan ) {
5496
							$plans[] = new FS_Plugin_Plan( $plan );
5497
						}
5498
5499
						$this->_admin_notices->add_sticky(
5500
							FS_Plan_Manager::instance()->has_free_plan( $plans ) ?
5501
								sprintf(
5502
									__fs( 'addon-successfully-upgraded-message' ),
5503
									$addon->title
5504
								) . ' ' . $this->_get_latest_download_link(
5505
									__fs( 'download-latest-version' ),
5506
									$addon_id
5507
								)
5508
								:
5509
								sprintf(
5510
									__fs( 'addon-successfully-purchased-message' ),
5511
									$addon->title
5512
								) . ' ' . $this->_get_latest_download_link(
5513
									__fs( 'download-latest-version' ),
5514
									$addon_id
5515
								),
5516
							'addon_plan_upgraded_' . $addon->slug,
5517
							__fs( 'yee-haw' ) . '!'
5518
						);
5519
					}
5520
				}
5521
			}
5522
		}
5523
5524
		/**
5525
		 * Sync site's plugin plan.
5526
		 *
5527
		 * @author Vova Feldman (@svovaf)
5528
		 * @since  1.0.6
5529
		 * @uses   FS_Api
5530
		 *
5531
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
5532
		 *                         the admin.
5533
		 */
5534
		private function _sync_plugin_license( $background = false ) {
5535
			$this->_logger->entrance();
5536
5537
			// Sync site info.
5538
			$site = $this->send_install_update( array(), true );
5539
5540
			$plan_change = 'none';
5541
5542
			if ( $this->is_api_error( $site ) ) {
5543
				$api = $this->get_api_site_scope();
5544
5545
				// Try to ping API to see if not blocked.
5546
				if ( ! $api->test() ) {
5547
					// Failed to ping API - blocked!
5548
					$this->_admin_notices->add(
5549
						sprintf(
5550
							__fs( 'server-blocking-access' ),
5551
							$this->get_plugin_name(),
5552
							'<a href="' . $api->get_url() . '" target="_blank">' . $api->get_url() . '</a>'
5553
						) . '<br> ' . __fs( 'server-error-message' ) . var_export( $site->error, true ),
5554
						__fs( 'oops' ) . '...',
5555
						'error',
5556
						$background
5557
					);
5558
				} else {
5559
					// Authentication params are broken.
5560
					$this->_admin_notices->add(
5561
						__fs( 'wrong-authentication-param-message' ),
5562
						__fs( 'oops' ) . '...',
5563
						'error'
5564
					);
5565
				}
5566
			} else {
5567
				$site = new FS_Site( $site );
5568
5569
				// Sync licenses.
5570
				$this->_sync_licenses();
5571
5572
				// Sync plans.
5573
				$this->_sync_plans();
5574
5575
				// Check if plan / license changed.
5576
				if ( ! FS_Entity::equals( $site->plan, $this->_site->plan ) ||
5577
				     // Check if trial started.
5578
				     $site->trial_plan_id != $this->_site->trial_plan_id ||
5579
				     $site->trial_ends != $this->_site->trial_ends ||
5580
				     // Check if license changed.
5581
				     $site->license_id != $this->_site->license_id
5582
				) {
5583
					if ( $site->is_trial() && ! $this->_site->is_trial() ) {
5584
						// New trial started.
5585
						$this->_site = $site;
5586
						$plan_change = 'trial_started';
5587
5588
						// Store trial plan information.
5589
						$this->_enrich_site_trial_plan( true );
5590
5591
					} else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) {
5592
						// Was in trial, but now trial expired and no license ID.
5593
						// New trial started.
5594
						$this->_site = $site;
5595
						$plan_change = 'trial_expired';
5596
5597
						// Clear trial plan information.
5598
						$this->_storage->trial_plan = null;
0 ignored issues
show
Documentation introduced by
The property trial_plan 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...
5599
5600
					} else {
5601
						$is_free = $this->is_free_plan();
5602
5603
						// Make sure license exist and not expired.
5604
						$new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id );
5605
5606
						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...
5607
							// The license is expired, so ignore upgrade method.
5608
						} else {
5609
							// License changed.
5610
							$this->_site = $site;
5611
							$this->_update_site_license( $new_license );
5612
							$this->_store_licenses();
5613
							$this->_enrich_site_plan( true );
5614
5615
							$plan_change = $is_free ?
5616
								'upgraded' :
5617
								( is_object( $new_license ) ?
5618
									'changed' :
5619
									'downgraded' );
5620
						}
5621
					}
5622
5623
					// Store updated site info.
5624
					$this->_store_site();
5625
				} else {
5626
					if ( is_object( $this->_license ) && $this->_license->is_expired() ) {
5627
						if ( ! $this->has_features_enabled_license() ) {
5628
							$this->_deactivate_license();
5629
							$plan_change = 'downgraded';
5630
						} else {
5631
							$plan_change = 'expired';
5632
						}
5633
					}
5634
5635
					if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) {
5636
						$this->_sync_site_subscription( $this->_license );
5637
					}
5638
				}
5639
			}
5640
5641
			switch ( $plan_change ) {
5642
				case 'none':
5643
					if ( ! $background && is_admin() ) {
5644
						$this->_admin_notices->add(
5645
							sprintf(
5646
								__fs( 'plan-did-not-change-message' ) . ' ' .
5647
								sprintf(
5648
									'<a href="%s">%s</a>',
5649
									$this->contact_url(
5650
										'bug',
5651
										sprintf( __fs( 'plan-did-not-change-email-message', 'freemius' ),
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with 'freemius'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
5652
											strtoupper( $this->_site->plan->name )
5653
										)
5654
									),
5655
									__fs( 'contact-us-here' )
5656
								)
5657
							),
5658
							__fs( 'hmm' ) . '...',
5659
							'error'
5660
						);
5661
					}
5662
					break;
5663
				case 'upgraded':
5664
					$this->_admin_notices->add_sticky(
5665
						sprintf(
5666
							__fs( 'plan-upgraded-message' ),
5667
							'<i>' . $this->get_plugin_name() . '</i>'
5668
						) . ( $this->is_premium() ? '' : ' ' . $this->_get_latest_download_link( sprintf(
5669
								__fs( 'download-latest-x-version' ),
5670
								$this->_site->plan->title
5671
							) )
5672
						),
5673
						'plan_upgraded',
5674
						__fs( 'yee-haw' ) . '!'
5675
					);
5676
5677
					$this->_admin_notices->remove_sticky( array(
5678
						'trial_started',
5679
						'trial_promotion',
5680
						'trial_expired',
5681
						'activation_complete',
5682
					) );
5683
					break;
5684
				case 'changed':
5685
					$this->_admin_notices->add_sticky(
5686
						sprintf(
5687
							__fs( 'plan-changed-to-x-message' ),
5688
							$this->_site->plan->title
5689
						),
5690
						'plan_changed'
5691
					);
5692
5693
					$this->_admin_notices->remove_sticky( array(
5694
						'trial_started',
5695
						'trial_promotion',
5696
						'trial_expired',
5697
						'activation_complete',
5698
					) );
5699
					break;
5700
				case 'downgraded':
5701
					$this->_admin_notices->add_sticky(
5702
						sprintf( __fs( 'license-expired-blocking-message' ) ),
5703
						'license_expired',
5704
						__fs( 'hmm' ) . '...'
5705
					);
5706
					$this->_admin_notices->remove_sticky( 'plan_upgraded' );
5707
					break;
5708
				case 'expired':
5709
					$this->_admin_notices->add_sticky(
5710
						sprintf( __fs( 'license-expired-non-blocking-message' ), $this->_site->plan->title ),
5711
						'license_expired',
5712
						__fs( 'hmm' ) . '...'
5713
					);
5714
					$this->_admin_notices->remove_sticky( 'plan_upgraded' );
5715
					break;
5716
				case 'trial_started':
5717
					$this->_admin_notices->add_sticky(
5718
						sprintf(
5719
							__fs( 'trial-started-message' ),
5720
							'<i>' . $this->get_plugin_name() . '</i>'
5721
						) . ( $this->is_premium() ? '' : ' ' . $this->_get_latest_download_link( sprintf(
5722
								__fs( 'download-latest-x-version' ),
5723
								$this->_storage->trial_plan->title
0 ignored issues
show
Documentation introduced by
The property trial_plan 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...
5724
							) ) ),
5725
						'trial_started',
5726
						__fs( 'yee-haw' ) . '!'
5727
					);
5728
5729
					$this->_admin_notices->remove_sticky( array(
5730
						'trial_promotion',
5731
					) );
5732
					break;
5733
				case 'trial_expired':
5734
					$this->_admin_notices->add_sticky(
5735
						__fs( 'trial-expired-message' ),
5736
						'trial_expired',
5737
						__fs( 'hmm' ) . '...'
5738
					);
5739
					$this->_admin_notices->remove_sticky( array(
5740
						'trial_started',
5741
						'trial_promotion',
5742
						'plan_upgraded',
5743
					) );
5744
					break;
5745
			}
5746
5747
			if ( 'none' !== $plan_change ) {
5748
				$this->do_action( 'after_license_change', $plan_change, $this->_site->plan );
5749
			}
5750
		}
5751
5752
		/**
5753
		 * @author Vova Feldman (@svovaf)
5754
		 * @since  1.0.5
5755
		 *
5756
		 * @param bool $background
5757
		 */
5758
		protected function _activate_license( $background = false ) {
5759
			$this->_logger->entrance();
5760
5761
			$premium_license = $this->_get_available_premium_license();
5762
5763
			if ( ! is_object( $premium_license ) ) {
5764
				return;
5765
			}
5766
5767
			$api     = $this->get_api_site_scope();
5768
			$license = $api->call( "/licenses/{$premium_license->id}.json", 'put' );
5769
5770
			if ( isset( $license->error ) ) {
5771
				if ( ! $background ) {
5772
					$this->_admin_notices->add(
5773
						__fs( 'license-activation-failed-message' ) . '<br> ' .
5774
						__fs( 'server-error-message' ) . ' ' . var_export( $license->error, true ),
5775
						__fs( 'hmm' ) . '...',
5776
						'error'
5777
					);
5778
				}
5779
5780
				return;
5781
			}
5782
5783
			$premium_license = new FS_Plugin_License( $license );
5784
5785
			// Updated site plan.
5786
			$this->_site->plan->id = $premium_license->plan_id;
5787
			$this->_update_site_license( $premium_license );
5788
			$this->_enrich_site_plan( false );
5789
5790
			$this->_store_account();
5791
5792
			if ( ! $background ) {
5793
				$this->_admin_notices->add_sticky(
5794
					__fs( 'license-activated-message' ) .
5795
					( $this->is_premium() ? '' : ' ' . $this->_get_latest_download_link( sprintf(
5796
							__fs( 'download-latest-x-version' ),
5797
							$this->_site->plan->title
5798
						) ) ),
5799
					'license_activated',
5800
					__fs( 'yee-haw' ) . '!'
5801
				);
5802
			}
5803
5804
			$this->_admin_notices->remove_sticky( array(
5805
				'trial_promotion',
5806
				'license_expired',
5807
			) );
5808
		}
5809
5810
		/**
5811
		 * @author Vova Feldman (@svovaf)
5812
		 * @since  1.0.5
5813
		 *
5814
		 * @param bool $show_notice
5815
		 */
5816
		protected function _deactivate_license( $show_notice = true ) {
5817
			$this->_logger->entrance();
5818
5819
			if ( ! is_object( $this->_license ) ) {
5820
				$this->_admin_notices->add(
5821
					sprintf( __fs( 'no-active-license-message' ), $this->_site->plan->title ),
5822
					__fs( 'hmm' ) . '...'
5823
				);
5824
5825
				return;
5826
			}
5827
5828
			$api     = $this->get_api_site_scope();
5829
			$license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' );
5830
5831
			if ( isset( $license->error ) ) {
5832
				$this->_admin_notices->add(
5833
					__fs( 'license-deactivation-failed-message' ) . '<br> ' .
5834
					__fs( 'server-error-message' ) . ' ' . var_export( $license->error, true ),
5835
					__fs( 'hmm' ) . '...',
5836
					'error'
5837
				);
5838
5839
				return;
5840
			}
5841
5842
			// Update license cache.
5843
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
5844
				if ( $license->id == $this->_licenses[ $i ]->id ) {
5845
					$this->_licenses[ $i ] = new FS_Plugin_License( $license );
5846
				}
5847
			}
5848
5849
			// Updated site plan to default.
5850
			$this->_sync_plans();
5851
			$this->_site->plan->id = $this->_plans[0]->id;
5852
			// Unlink license from site.
5853
			$this->_update_site_license( null );
5854
			$this->_enrich_site_plan( false );
5855
5856
			$this->_store_account();
5857
5858
			if ( $show_notice ) {
5859
				$this->_admin_notices->add(
5860
					sprintf( __fs( 'license-deactivation-message' ), $this->_site->plan->title ),
5861
					__fs( 'ok' )
5862
				);
5863
			}
5864
5865
			$this->_admin_notices->remove_sticky( array(
5866
				'plan_upgraded',
5867
				'license_activated',
5868
			) );
5869
		}
5870
5871
		/**
5872
		 * Site plan downgrade.
5873
		 *
5874
		 * @author Vova Feldman (@svovaf)
5875
		 * @since  1.0.4
5876
		 *
5877
		 * @uses   FS_Api
5878
		 */
5879
		private function _downgrade_site() {
5880
			$this->_logger->entrance();
5881
5882
			$api  = $this->get_api_site_scope();
5883
			$site = $api->call( 'downgrade.json', 'put' );
5884
5885
			$plan_downgraded = false;
5886
			$plan            = false;
5887
			if ( ! isset( $site->error ) ) {
5888
				$prev_plan_id = $this->_site->plan->id;
5889
5890
				// Update new site plan id.
5891
				$this->_site->plan->id = $site->plan_id;
5892
5893
				$plan         = $this->_enrich_site_plan();
5894
				$subscription = $this->_sync_site_subscription( $this->_license );
5895
5896
				// Plan downgraded if plan was changed or subscription was cancelled.
5897
				$plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) ||
5898
				                   ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() );
5899
			} 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...
5900
				// handle different error cases.
5901
5902
			}
5903
5904
			if ( $plan_downgraded ) {
5905
				// Remove previous sticky message about upgrade (if exist).
5906
				$this->_admin_notices->remove_sticky( 'plan_upgraded' );
5907
5908
				$this->_admin_notices->add(
5909
					sprintf( __fs( 'plan-x-downgraded-message' ),
5910
						$plan->title,
5911
						human_time_diff( time(), strtotime( $this->_license->expiration ) )
5912
					)
5913
				);
5914
5915
				// Store site updates.
5916
				$this->_store_site();
5917
			} else {
5918
				$this->_admin_notices->add(
5919
					__fs( 'plan-downgraded-failure-message' ),
5920
					__fs( 'oops' ) . '...',
5921
					'error'
5922
				);
5923
			}
5924
		}
5925
5926
		/**
5927
		 * Cancel site trial.
5928
		 *
5929
		 * @author Vova Feldman (@svovaf)
5930
		 * @since  1.0.9
5931
		 *
5932
		 * @uses   FS_Api
5933
		 */
5934
		private function _cancel_trial() {
5935
			$this->_logger->entrance();
5936
5937
			if ( ! $this->is_trial() ) {
5938
				$this->_admin_notices->add(
5939
					__fs( 'trial-cancel-no-trial-message' ),
5940
					__fs( 'oops' ) . '...',
5941
					'error'
5942
				);
5943
5944
				return;
5945
			}
5946
5947
			$api  = $this->get_api_site_scope();
5948
			$site = $api->call( 'trials.json', 'delete' );
5949
5950
			$trial_cancelled = false;
5951
5952
			if ( ! isset( $site->error ) ) {
5953
				$prev_trial_ends = $this->_site->trial_ends;
5954
5955
				// Update new site plan id.
5956
				$this->_site->trial_ends = $site->trial_ends;
5957
5958
				$trial_cancelled = ( $prev_trial_ends != $site->trial_ends );
5959
			} 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...
5960
				// handle different error cases.
5961
5962
			}
5963
5964
			if ( $trial_cancelled ) {
5965
				// Remove previous sticky message about upgrade (if exist).
5966
				$this->_admin_notices->remove_sticky( 'plan_upgraded' );
5967
5968
				$this->_admin_notices->add(
5969
					sprintf( __fs( 'trial-cancel-message' ), $this->_storage->trial_plan->title )
0 ignored issues
show
Documentation introduced by
The property trial_plan 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...
5970
				);
5971
5972
				$this->_admin_notices->remove_sticky( array(
5973
					'trial_started',
5974
					'trial_promotion',
5975
					'plan_upgraded',
5976
				) );
5977
5978
				// Store site updates.
5979
				$this->_store_site();
5980
5981
				// Clear trial plan information.
5982
				unset( $this->_storage->trial_plan );
5983
			} else {
5984
				$this->_admin_notices->add(
5985
					__fs( 'trial-cancel-failure-message' ),
5986
					__fs( 'oops' ) . '...',
5987
					'error'
5988
				);
5989
			}
5990
		}
5991
5992
		/**
5993
		 * @author Vova Feldman (@svovaf)
5994
		 * @since  1.0.6
5995
		 *
5996
		 * @param bool|number $plugin_id
5997
		 *
5998
		 * @return bool
5999
		 */
6000
		private function _is_addon_id( $plugin_id ) {
6001
			return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id );
6002
		}
6003
6004
		/**
6005
		 * Check if user eligible to download premium version updates.
6006
		 *
6007
		 * @author Vova Feldman (@svovaf)
6008
		 * @since  1.0.6
6009
		 *
6010
		 * @return bool
6011
		 */
6012
		private function _can_download_premium() {
6013
			return $this->has_active_license() ||
6014
			       ( $this->is_trial() && ! $this->get_trial_plan()->is_free() );
6015
		}
6016
6017
		/**
6018
		 *
6019
		 * @author Vova Feldman (@svovaf)
6020
		 * @since  1.0.6
6021
		 *
6022
		 * @param bool|number $addon_id
6023
		 * @param string      $type "json" or "zip"
6024
		 *
6025
		 * @return string
6026
		 */
6027
		private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) {
6028
6029
			$is_addon = $this->_is_addon_id( $addon_id );
6030
6031
			$is_premium = null;
6032
			if ( ! $is_addon ) {
6033
				$is_premium = $this->_can_download_premium();
6034
			} else if ( $this->is_addon_activated( $addon_id ) ) {
6035
				$is_premium = self::get_instance_by_id( $addon_id )->_can_download_premium();
6036
			}
6037
6038
			return // If add-on, then append add-on ID.
6039
				( $is_addon ? "/addons/$addon_id" : '' ) .
6040
				'/updates/latest.' . $type .
6041
				// If add-on and not yet activated, try to fetch based on server licensing.
6042
				( is_bool( $is_premium ) ? '?is_premium=' . json_encode( $is_premium ) : '' );
6043
		}
6044
6045
		/**
6046
		 * @author Vova Feldman (@svovaf)
6047
		 * @since  1.0.4
6048
		 *
6049
		 * @param bool|number $addon_id
6050
		 *
6051
		 * @return object|false Plugin latest tag info.
6052
		 */
6053
		function _fetch_latest_version( $addon_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...
6054
			$tag            = $this->get_api_site_or_plugin_scope()->get(
6055
				$this->_get_latest_version_endpoint( $addon_id, 'json' ),
6056
				true
6057
			);
6058
			$latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get';
6059
			$this->_logger->departure( 'Latest version ' . $latest_version );
6060
6061
			return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false;
6062
		}
6063
6064
		#region Download Plugin ------------------------------------------------------------------
6065
6066
		/**
6067
		 * Download latest plugin version, based on plan.
6068
		 * The download will be fetched via the API first.
6069
		 *
6070
		 * @author Vova Feldman (@svovaf)
6071
		 * @since  1.0.4
6072
		 *
6073
		 * @param bool|number $plugin_id
6074
		 *
6075
		 * @uses   FS_Api
6076
		 *
6077
		 * @deprecated
6078
		 */
6079
		private function _download_latest( $plugin_id = false ) {
6080
			$this->_logger->entrance();
6081
6082
			$is_addon = $this->_is_addon_id( $plugin_id );
6083
6084
			$is_premium = $this->_can_download_premium();
6085
6086
			$latest = $this->get_api_site_scope()->call(
6087
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
6088
			);
6089
6090
			$slug = $this->_slug;
6091
			if ( $is_addon ) {
6092
				$addon = $this->get_addon( $plugin_id );
6093
				$slug  = is_object( $addon ) ? $addon->slug : 'addon';
6094
			}
6095
6096
			if ( ! is_object( $latest ) ) {
6097
				header( "Content-Type: application/zip" );
6098
				header( "Content-Disposition: attachment; filename={$slug}" . ( ! $is_addon && $is_premium ? '-premium' : '' ) . ".zip" );
6099
				header( "Content-Length: " . strlen( $latest ) );
6100
				echo $latest;
6101
6102
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _download_latest() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
6103
			}
6104
		}
6105
6106
		/**
6107
		 * Download latest plugin version, based on plan.
6108
		 *
6109
		 * Not like _download_latest(), this will redirect the page
6110
		 * to secure download url to prevent dual download (from FS to WP server,
6111
		 * and then from WP server to the client / browser).
6112
		 *
6113
		 * @author Vova Feldman (@svovaf)
6114
		 * @since  1.0.9
6115
		 *
6116
		 * @param bool|number $plugin_id
6117
		 *
6118
		 * @uses   FS_Api
6119
		 * @uses   wp_redirect()
6120
		 */
6121
		private function _download_latest_directly( $plugin_id = false ) {
6122
			$this->_logger->entrance();
6123
6124
			wp_redirect( $this->_get_latest_download_api_url( $plugin_id ) );
6125
		}
6126
6127
		/**
6128
		 * Get latest plugin FS API download URL.
6129
		 *
6130
		 * @author Vova Feldman (@svovaf)
6131
		 * @since  1.0.9
6132
		 *
6133
		 * @param bool|number $plugin_id
6134
		 *
6135
		 * @return string
6136
		 */
6137
		private function _get_latest_download_api_url( $plugin_id = false ) {
6138
			$this->_logger->entrance();
6139
6140
			return $this->get_api_site_scope()->get_signed_url(
6141
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
6142
			);
6143
		}
6144
6145
		/**
6146
		 * Get latest plugin download link.
6147
		 *
6148
		 * @author Vova Feldman (@svovaf)
6149
		 * @since  1.0.9
6150
		 *
6151
		 * @param string      $label
6152
		 * @param bool|number $plugin_id
6153
		 *
6154
		 * @return string
6155
		 */
6156
		private function _get_latest_download_link( $label, $plugin_id = false ) {
6157
			return sprintf(
6158
				'<a target="_blank" href="%s">%s</a>',
6159
				$this->_get_latest_download_local_url( $plugin_id ),
6160
				$label
6161
			);
6162
		}
6163
6164
		/**
6165
		 * Get latest plugin download local URL.
6166
		 *
6167
		 * @author Vova Feldman (@svovaf)
6168
		 * @since  1.0.9
6169
		 *
6170
		 * @param bool|number $plugin_id
6171
		 *
6172
		 * @return string
6173
		 */
6174
		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...
6175
			// Add timestamp to protect from caching.
6176
			$params = array( 'ts' => WP_FS__SCRIPT_START_TIME );
6177
6178
			if ( ! empty( $plugin_id ) ) {
6179
				$params['plugin_id'] = $plugin_id;
6180
			}
6181
6182
			return $this->get_account_url( 'download_latest', $params );
6183
		}
6184
6185
		#endregion Download Plugin ------------------------------------------------------------------
6186
6187
		/**
6188
		 * @author Vova Feldman (@svovaf)
6189
		 * @since  1.0.4
6190
		 *
6191
		 * @uses   FS_Api
6192
		 *
6193
		 * @param bool        $background Hints the method if it's a background updates check. If false, it means that
6194
		 *                                was initiated by the admin.
6195
		 * @param bool|number $plugin_id
6196
		 */
6197
		private function _check_updates( $background = false, $plugin_id = false ) {
6198
			$this->_logger->entrance();
6199
6200
			// Check if there's a newer version for download.
6201
			$new_version = $this->_fetch_newer_version( $plugin_id );
6202
6203
			$update = null;
6204
			if ( is_object( $new_version ) ) {
6205
				$update = new FS_Plugin_Tag( $new_version );
6206
6207
				if ( ! $background ) {
6208
					$this->_admin_notices->add(
6209
						sprintf(
6210
							__fs( 'version-x-released' ) . ' ' . __fS( 'please-download-x' ),
6211
							$update->version,
6212
							sprintf(
6213
								'<a href="%s" target="_blank">%s</a>',
6214
								$this->get_account_url( 'download_latest' ),
6215
								sprintf( __fs( 'latest-x-version' ), $this->_site->plan->title )
6216
							)
6217
						),
6218
						__fs( 'new' ) . '!'
6219
					);
6220
				}
6221
			} else if ( false === $new_version && ! $background ) {
6222
				$this->_admin_notices->add(
6223
					__fs( 'you-have-latest' ),
6224
					__fs( 'you-are-good' )
6225
				);
6226
			}
6227
6228
			$this->_store_update( $update, true, $plugin_id );
6229
		}
6230
6231
		/**
6232
		 * @author Vova Feldman (@svovaf)
6233
		 * @since  1.0.4
6234
		 *
6235
		 * @uses   FS_Api
6236
		 *
6237
		 */
6238
		private function _sync_addons() {
6239
			$this->_logger->entrance();
6240
6241
			$result = $this->get_api_site_or_plugin_scope()->get( '/addons.json?enriched=true', true );
6242
6243
			if ( isset( $result->error ) ) {
6244
				return;
6245
			}
6246
6247
			$addons = array();
6248
			for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) {
6249
				$addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] );
6250
			}
6251
6252
			$this->_store_addons( $addons, true );
6253
		}
6254
6255
		/**
6256
		 * Handle user email update.
6257
		 *
6258
		 * @author Vova Feldman (@svovaf)
6259
		 * @since  1.0.3
6260
		 * @uses   FS_Api
6261
		 *
6262
		 * @param string $new_email
6263
		 *
6264
		 * @return object
6265
		 */
6266
		private function _update_email( $new_email ) {
6267
			$this->_logger->entrance();
6268
6269
6270
			$api  = $this->get_api_user_scope();
6271
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array(
6272
				'email'                   => $new_email,
6273
				'after_email_confirm_url' => $this->_get_admin_page_url(
6274
					'account',
6275
					array( 'fs_action' => 'sync_user' )
6276
				),
6277
			) );
6278
6279
			if ( ! isset( $user->error ) ) {
6280
				$this->_user->email       = $user->email;
6281
				$this->_user->is_verified = $user->is_verified;
6282
				$this->_store_user();
6283
			} 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...
6284
				// handle different error cases.
6285
6286
			}
6287
6288
			return $user;
6289
		}
6290
6291
		/**
6292
		 * @author Vova Feldman (@svovaf)
6293
		 * @since  1.1.1
6294
		 *
6295
		 * @param mixed $result
6296
		 *
6297
		 * @return bool Is API result contains an error.
6298
		 */
6299
		private function is_api_error( $result ) {
6300
			return ( is_object( $result ) && isset( $result->error ) ) ||
6301
			       is_string( $result );
6302
		}
6303
6304
		/**
6305
		 * Start install ownership change.
6306
		 *
6307
		 * @author Vova Feldman (@svovaf)
6308
		 * @since  1.1.1
6309
		 * @uses   FS_Api
6310
		 *
6311
		 * @param string $candidate_email
6312
		 *
6313
		 * @return bool Is ownership change successfully initiated.
6314
		 */
6315
		private function init_change_owner( $candidate_email ) {
6316
			$this->_logger->entrance();
6317
6318
			$api    = $this->get_api_site_scope();
6319
			$result = $api->call( "/users/{$this->_user->id}.json", 'put', array(
6320
				'email'             => $candidate_email,
6321
				'after_confirm_url' => $this->_get_admin_page_url(
6322
					'account',
6323
					array( 'fs_action' => 'change_owner' )
6324
				),
6325
			) );
6326
6327
			return ! $this->is_api_error( $result );
6328
		}
6329
6330
		/**
6331
		 * Handle install ownership change.
6332
		 *
6333
		 * @author Vova Feldman (@svovaf)
6334
		 * @since  1.1.1
6335
		 * @uses   FS_Api
6336
		 *
6337
		 * @return bool Was ownership change successfully complete.
6338
		 */
6339
		private function complete_change_owner() {
6340
			$this->_logger->entrance();
6341
6342
			$site_result = $this->get_api_site_scope( true )->get();
6343
			$site        = new FS_Site( $site_result );
6344
			$this->_site = $site;
6345
6346
			$user     = new FS_User();
6347
			$user->id = fs_request_get( 'user_id' );
6348
6349
			// Validate install's user and given user.
6350
			if ( $user->id != $this->_site->user_id ) {
6351
				return false;
6352
			}
6353
6354
			$user->public_key = fs_request_get( 'user_public_key' );
6355
			$user->secret_key = fs_request_get( 'user_secret_key' );
6356
6357
			// Fetch new user information.
6358
			$this->_user = $user;
6359
			$user_result = $this->get_api_user_scope( true )->get();
6360
			$user        = new FS_User( $user_result );
6361
			$this->_user = $user;
6362
6363
			$this->_set_account( $user, $site );
6364
6365
			return true;
6366
		}
6367
6368
		/**
6369
		 * Handle user name update.
6370
		 *
6371
		 * @author Vova Feldman (@svovaf)
6372
		 * @since  1.0.9
6373
		 * @uses   FS_Api
6374
		 *
6375
		 * @return object
6376
		 */
6377
		private function update_user_name() {
6378
			$this->_logger->entrance();
6379
			$name = fs_request_get( 'fs_user_name_' . $this->_slug, '' );
6380
6381
			$api  = $this->get_api_user_scope();
6382
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array(
6383
				'name' => $name,
6384
			) );
6385
6386
			if ( ! isset( $user->error ) ) {
6387
				$this->_user->first = $user->first;
6388
				$this->_user->last  = $user->last;
6389
				$this->_store_user();
6390
			} 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...
6391
				// handle different error cases.
6392
6393
			}
6394
6395
			return $user;
6396
		}
6397
6398
		/**
6399
		 * Verify user email.
6400
		 *
6401
		 * @author Vova Feldman (@svovaf)
6402
		 * @since  1.0.3
6403
		 * @uses   FS_Api
6404
		 */
6405
		private function verify_email() {
6406
			$this->_handle_account_user_sync();
6407
6408
			if ( $this->_user->is_verified() ) {
6409
				return;
6410
			}
6411
6412
			$api    = $this->get_api_site_scope();
6413
			$result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array(
6414
				'after_email_confirm_url' => $this->_get_admin_page_url(
6415
					'account',
6416
					array( 'fs_action' => 'sync_user' )
6417
				)
6418
			) );
6419
6420
			if ( ! isset( $result->error ) ) {
6421
				$this->_admin_notices->add( sprintf(
6422
					__fs( 'verification-email-sent-message' ),
6423
					sprintf( '<a href="mailto:%1s">%2s</a>', esc_url( $this->_user->email ), $this->_user->email )
6424
				) );
6425
			} 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...
6426
				// handle different error cases.
6427
6428
			}
6429
		}
6430
6431
		/**
6432
		 * @author Vova Feldman (@svovaf)
6433
		 * @since  1.1.2
6434
		 *
6435
		 * @return string
6436
		 */
6437
		private function get_activation_url() {
6438
			return $this->apply_filters( 'connect_url', $this->_get_admin_page_url() );
6439
		}
6440
6441
		/**
6442
		 * @author Vova Feldman (@svovaf)
6443
		 * @since  1.1.3
6444
		 *
6445
		 * @param string $filter Filter name.
6446
		 *
6447
		 * @return string
6448
		 */
6449
		private function get_after_activation_url( $filter ) {
6450
			$first_time_path = $this->_menu->get_first_time_path();
6451
6452
			return $this->apply_filters(
6453
				$filter,
6454
				empty( $first_time_path ) ?
6455
					$this->_get_admin_page_url() :
6456
					$first_time_path
6457
			);
6458
		}
6459
6460
		/**
6461
		 * Handle account page updates / edits / actions.
6462
		 *
6463
		 * @author Vova Feldman (@svovaf)
6464
		 * @since  1.0.2
6465
		 *
6466
		 */
6467
		private function _handle_account_edits() {
6468
			if ( ! current_user_can( 'activate_plugins' ) ) {
6469
				return;
6470
			}
6471
6472
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
6473
			$action    = fs_get_action();
6474
6475
			switch ( $action ) {
6476
				case 'delete_account':
6477
					check_admin_referer( $action );
6478
6479
					if ( $plugin_id == $this->get_id() ) {
6480
						$this->delete_account_event();
6481
6482
						// Clear user and site.
6483
						$this->_site = null;
6484
						$this->_user = null;
6485
6486
						if ( fs_redirect( $this->get_activation_url() ) ) {
6487
							exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _handle_account_edits() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
6488
						}
6489
					} else {
6490
						if ( $this->is_addon_activated( $plugin_id ) ) {
6491
							$fs_addon = self::get_instance_by_id( $plugin_id );
6492
							$fs_addon->delete_account_event();
6493
6494
							if ( fs_redirect( $this->_get_admin_page_url( 'account' ) ) ) {
6495
								exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _handle_account_edits() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
6496
							}
6497
						}
6498
					}
6499
6500
					return;
6501
6502
				case 'downgrade_account':
6503
					check_admin_referer( $action );
6504
					$this->_downgrade_site();
6505
6506
					return;
6507
6508
				case 'activate_license':
6509
					check_admin_referer( $action );
6510
6511
					if ( $plugin_id == $this->get_id() ) {
6512
						$this->_activate_license();
6513
					} else {
6514
						if ( $this->is_addon_activated( $plugin_id ) ) {
6515
							$fs_addon = self::get_instance_by_id( $plugin_id );
6516
							$fs_addon->_activate_license();
6517
						}
6518
					}
6519
6520
					return;
6521
6522
				case 'deactivate_license':
6523
					check_admin_referer( $action );
6524
6525
					if ( $plugin_id == $this->get_id() ) {
6526
						$this->_deactivate_license();
6527
					} else {
6528
						if ( $this->is_addon_activated( $plugin_id ) ) {
6529
							$fs_addon = self::get_instance_by_id( $plugin_id );
6530
							$fs_addon->_deactivate_license();
6531
						}
6532
					}
6533
6534
					return;
6535
6536
				case 'check_updates':
6537
					check_admin_referer( $action );
6538
					$this->_check_updates();
6539
6540
					return;
6541
6542
				case 'change_owner':
6543
					$state = fs_request_get( 'state', 'init' );
6544
					switch ( $state ) {
6545
						case 'init':
6546
							$candidate_email = fs_request_get( 'candidate_email', '' );
6547
6548
							if ( $this->init_change_owner( $candidate_email ) ) {
6549
								$this->_admin_notices->add( sprintf( __fs( 'change-owner-request-sent-x' ), '<b>' . $this->_user->email . '</b>' ) );
6550
							}
6551
							break;
6552
						case 'owner_confirmed':
6553
							$candidate_email = fs_request_get( 'candidate_email', '' );
6554
6555
							$this->_admin_notices->add( sprintf( __fs( 'change-owner-request_owner-confirmed' ), '<b>' . $candidate_email . '</b>' ) );
6556
							break;
6557
						case 'candidate_confirmed':
6558
							if ( $this->complete_change_owner() ) {
6559
								$this->_admin_notices->add_sticky(
6560
									sprintf( __fs( 'change-owner-request_candidate-confirmed' ), '<b>' . $this->_user->email . '</b>' ),
6561
									'ownership_changed',
6562
									__fs( 'congrats' ) . '!'
6563
								);
6564
							} 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...
6565
								// @todo Handle failed ownership change message.
6566
							}
6567
							break;
6568
					}
6569
6570
					return;
6571
6572
				case 'update_email':
6573
					check_admin_referer( 'update_email' );
6574
6575
					$new_email = fs_request_get( 'fs_email_' . $this->_slug, '' );
6576
					$result    = $this->_update_email( $new_email );
6577
6578
					if ( isset( $result->error ) ) {
6579
						switch ( $result->error->code ) {
6580
							case 'user_exist':
6581
								$this->_admin_notices->add(
6582
									__fs( 'user-exist-message' ) . ' ' .
6583
									sprintf( __fs( 'user-exist-message_ownership' ), '<b>' . $new_email . '</b>' ) .
6584
									sprintf(
6585
										'<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
6586
										$this->get_account_url( 'change_owner', array(
6587
											'state'           => 'init',
6588
											'candidate_email' => $new_email
6589
										) ),
6590
										__fs( 'change-ownership' )
6591
									),
6592
									__fs( 'oops' ) . '...',
6593
									'error'
6594
								);
6595
								break;
6596
						}
6597
					} else {
6598
						$this->_admin_notices->add( __fs( 'email-updated-message' ) );
6599
					}
6600
6601
					return;
6602
6603
				case 'update_user_name':
6604
					check_admin_referer( 'update_user_name' );
6605
6606
					$result = $this->update_user_name();
6607
6608
					if ( isset( $result->error ) ) {
6609
						$this->_admin_notices->add(
6610
							__fs( 'name-update-failed-message' ),
6611
							__fs( 'oops' ) . '...',
6612
							'error'
6613
						);
6614
					} else {
6615
						$this->_admin_notices->add( __fs( 'name-updated-message' ) );
6616
					}
6617
6618
					return;
6619
6620
				#region Actions that might be called from external links (e.g. email)
6621
6622
				case 'cancel_trial':
6623
					$this->_cancel_trial();
6624
6625
					return;
6626
6627
				case 'verify_email':
6628
					$this->verify_email();
6629
6630
					return;
6631
6632
				case 'sync_user':
6633
					$this->_handle_account_user_sync();
6634
6635
					return;
6636
6637
				case $this->_slug . '_sync_license':
6638
					$this->_sync_license();
6639
6640
					return;
6641
6642
				case 'download_latest':
6643
					$this->_download_latest_directly( $plugin_id );
6644
6645
					return;
6646
6647
				#endregion
6648
			}
6649
6650
			if ( WP_FS__IS_POST_REQUEST ) {
6651
				$properties = array( 'site_secret_key', 'site_id', 'site_public_key' );
6652
				foreach ( $properties as $p ) {
6653
					if ( 'update_' . $p === $action ) {
6654
						check_admin_referer( $action );
6655
6656
						$this->_logger->log( $action );
6657
6658
						$site_property                      = substr( $p, strlen( 'site_' ) );
6659
						$site_property_value                = fs_request_get( 'fs_' . $p . '_' . $this->_slug, '' );
6660
						$this->get_site()->{$site_property} = $site_property_value;
6661
6662
						// Store account after modification.
6663
						$this->_store_site();
6664
6665
						$this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value );
6666
6667
						$this->_admin_notices->add( sprintf(
6668
							__fs( 'x-updated' ),
6669
							'<b>' . str_replace( '_', ' ', $p ) . '</b>' ) );
6670
6671
						return;
6672
					}
6673
				}
6674
			}
6675
		}
6676
6677
		/**
6678
		 * Account page resources load.
6679
		 *
6680
		 * @author Vova Feldman (@svovaf)
6681
		 * @since  1.0.6
6682
		 */
6683
		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...
6684
			$this->_logger->entrance();
6685
6686
			$this->_logger->info( var_export( $_REQUEST, true ) );
6687
6688
			fs_enqueue_local_style( 'fs_account', '/admin/account.css' );
6689
6690
			if ( $this->_has_addons() ) {
6691
				wp_enqueue_script( 'plugin-install' );
6692
				add_thickbox();
6693
6694
				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...
6695
					$classes .= ' plugins-php';
6696
6697
					return $classes;
6698
				}
6699
6700
				add_filter( 'admin_body_class', 'fs_addons_body_class' );
6701
			}
6702
6703
			$this->_handle_account_edits();
6704
6705
			$this->do_action( 'account_page_load_before_departure' );
6706
		}
6707
6708
		/**
6709
		 * Render account page.
6710
		 *
6711
		 * @author Vova Feldman (@svovaf)
6712
		 * @since  1.0.0
6713
		 */
6714
		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...
6715
			$this->_logger->entrance();
6716
6717
			$vars = array( 'slug' => $this->_slug );
6718
			fs_require_once_template( 'account.php', $vars );
6719
		}
6720
6721
		/**
6722
		 * Render account connect page.
6723
		 *
6724
		 * @author Vova Feldman (@svovaf)
6725
		 * @since  1.0.7
6726
		 */
6727
		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...
6728
			$this->_logger->entrance();
6729
6730
			$vars = array( 'slug' => $this->_slug );
6731
			if ( $this->is_pending_activation() ) {
6732
				fs_require_once_template( 'pending-activation.php', $vars );
6733
			} else {
6734
				fs_require_once_template( 'connect.php', $vars );
6735
			}
6736
		}
6737
6738
		/**
6739
		 * Load required resources before add-ons page render.
6740
		 *
6741
		 * @author Vova Feldman (@svovaf)
6742
		 * @since  1.0.6
6743
		 */
6744
		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...
6745
			$this->_logger->entrance();
6746
6747
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
6748
6749
			wp_enqueue_script( 'plugin-install' );
6750
			add_thickbox();
6751
6752
			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 (L6694-6698) 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...
6753
				$classes .= ' plugins-php';
6754
6755
				return $classes;
6756
			}
6757
6758
			add_filter( 'admin_body_class', 'fs_addons_body_class' );
6759
6760
			if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) {
6761
				$this->_admin_notices->add(
6762
					sprintf( __fs( 'addons-info-external-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
6763
					__fs( 'heads-up' ),
6764
					'update-nag'
6765
				);
6766
			}
6767
		}
6768
6769
		/**
6770
		 * Render add-ons page.
6771
		 *
6772
		 * @author Vova Feldman (@svovaf)
6773
		 * @since  1.0.6
6774
		 */
6775
		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...
6776
			$this->_logger->entrance();
6777
6778
			$vars = array( 'slug' => $this->_slug );
6779
			fs_require_once_template( 'add-ons.php', $vars );
6780
		}
6781
6782
		/* Pricing & Upgrade
6783
		------------------------------------------------------------------------------------------------------------------*/
6784
		/**
6785
		 * Render pricing page.
6786
		 *
6787
		 * @author Vova Feldman (@svovaf)
6788
		 * @since  1.0.0
6789
		 */
6790
		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...
6791
			$this->_logger->entrance();
6792
6793
			$vars = array( 'slug' => $this->_slug );
6794
6795
			if ( 'true' === fs_request_get( 'checkout', false ) ) {
6796
				fs_require_once_template( 'checkout.php', $vars );
6797
			} else {
6798
				fs_require_once_template( 'pricing.php', $vars );
6799
			}
6800
		}
6801
6802
		#region Contact Us ------------------------------------------------------------------
6803
6804
		/**
6805
		 * Render contact-us page.
6806
		 *
6807
		 * @author Vova Feldman (@svovaf)
6808
		 * @since  1.0.3
6809
		 */
6810
		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...
6811
			$this->_logger->entrance();
6812
6813
			$vars = array( 'slug' => $this->_slug );
6814
			fs_require_once_template( 'contact.php', $vars );
6815
		}
6816
6817
		#endregion ------------------------------------------------------------------
6818
6819
		/**
6820
		 * Hide all admin notices to prevent distractions.
6821
		 *
6822
		 * @author Vova Feldman (@svovaf)
6823
		 * @since  1.0.3
6824
		 *
6825
		 * @uses   remove_all_actions()
6826
		 */
6827
		function _hide_admin_notices() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6828
			remove_all_actions( 'admin_notices' );
6829
			remove_all_actions( 'network_admin_notices' );
6830
			remove_all_actions( 'all_admin_notices' );
6831
			remove_all_actions( 'user_admin_notices' );
6832
		}
6833
6834
		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...
6835
			$this->_hide_admin_notices();
6836
6837
			// Hide footer.
6838
			echo '<style>#wpfooter { display: none !important; }</style>';
6839
		}
6840
6841
		/**
6842
		 * Attach to admin_head hook to hide all admin notices.
6843
		 *
6844
		 * @author Vova Feldman (@svovaf)
6845
		 * @since  1.0.3
6846
		 */
6847
		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...
6848
			add_action( 'admin_head', array( &$this, '_clean_admin_content_section_hook' ) );
6849
		}
6850
6851
		/* CSS & JavaScript
6852
		------------------------------------------------------------------------------------------------------------------*/
6853
		/*		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...
6854
					$url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src );
6855
6856
					$this->_logger->entrance( 'script = ' . $url );
6857
6858
					wp_enqueue_script( $handle, $url );
6859
				}*/
6860
6861
		/* SDK
6862
		------------------------------------------------------------------------------------------------------------------*/
6863
		private $_user_api;
6864
6865
		/**
6866
		 *
6867
		 * @author Vova Feldman (@svovaf)
6868
		 * @since  1.0.2
6869
		 *
6870
		 * @param bool $flush
6871
		 *
6872
		 * @return FS_Api
6873
		 */
6874
		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...
6875
			if ( ! isset( $this->_user_api ) || $flush ) {
6876
				$this->_user_api = FS_Api::instance(
6877
					$this->_slug,
6878
					'user',
6879
					$this->_user->id,
6880
					$this->_user->public_key,
6881
					! $this->is_live(),
6882
					$this->_user->secret_key
6883
				);
6884
			}
6885
6886
			return $this->_user_api;
6887
		}
6888
6889
		private $_site_api;
6890
6891
		/**
6892
		 *
6893
		 * @author Vova Feldman (@svovaf)
6894
		 * @since  1.0.2
6895
		 *
6896
		 * @param bool $flush
6897
		 *
6898
		 * @return FS_Api
6899
		 */
6900
		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...
6901
			if ( ! isset( $this->_site_api ) || $flush ) {
6902
				$this->_site_api = FS_Api::instance(
6903
					$this->_slug,
6904
					'install',
6905
					$this->_site->id,
6906
					$this->_site->public_key,
6907
					! $this->is_live(),
6908
					$this->_site->secret_key
6909
				);
6910
			}
6911
6912
			return $this->_site_api;
6913
		}
6914
6915
		private $_plugin_api;
6916
6917
		/**
6918
		 * Get plugin public API scope.
6919
		 *
6920
		 * @author Vova Feldman (@svovaf)
6921
		 * @since  1.0.7
6922
		 *
6923
		 * @return FS_Api
6924
		 */
6925
		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...
6926
			if ( ! isset( $this->_plugin_api ) ) {
6927
				$this->_plugin_api = FS_Api::instance(
6928
					$this->_slug,
6929
					'plugin',
6930
					$this->_plugin->id,
6931
					$this->_plugin->public_key,
6932
					! $this->is_live()
6933
				);
6934
			}
6935
6936
			return $this->_plugin_api;
6937
		}
6938
6939
		/**
6940
		 * Get site API scope object (fallback to public plugin scope when not registered).
6941
		 *
6942
		 * @author Vova Feldman (@svovaf)
6943
		 * @since  1.0.7
6944
		 *
6945
		 * @return FS_Api
6946
		 */
6947
		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...
6948
			return $this->is_registered() ?
6949
				$this->get_api_site_scope() :
6950
				$this->get_api_plugin_scope();
6951
		}
6952
6953
		/**
6954
		 * Show trial promotional notice (if any trial exist).
6955
		 *
6956
		 * @author Vova Feldman (@svovaf)
6957
		 * @since  1.0.9
6958
		 *
6959
		 * @param $plans
6960
		 */
6961
		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...
6962
			$this->_storage->has_trial_plan = FS_Plan_Manager::instance()->has_trial_plan( $plans );
0 ignored issues
show
Documentation introduced by
The property has_trial_plan 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...
6963
		}
6964
6965
		/**
6966
		 * Show trial promotional notice (if any trial exist).
6967
		 *
6968
		 * @author Vova Feldman (@svovaf)
6969
		 * @since  1.0.9
6970
		 */
6971
		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...
6972
			// Check if trial already utilized.
6973
			if ( $this->_site->is_trial_utilized() ) {
6974
				return;
6975
			}
6976
6977
			// Check if already paying.
6978
			if ( $this->is_paying() ) {
6979
				return;
6980
			}
6981
6982
			// Check if trial message is already shown.
6983
			if ( $this->_admin_notices->has_sticky( 'trial_promotion' ) ) {
6984
				return;
6985
			}
6986
6987
			$trial_plans       = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
6988
			$trial_plans_count = count( $trial_plans );
6989
6990
			// Check if any of the plans contains trial.
6991
			if ( 0 === $trial_plans_count ) {
6992
				return;
6993
			}
6994
6995
			/**
6996
			 * @var FS_Plugin_Plan $paid_plan
6997
			 */
6998
			$paid_plan            = $trial_plans[0];
6999
			$require_subscription = $paid_plan->is_require_subscription;
7000
			$upgrade_url          = $this->get_trial_url();
7001
			$cc_string            = $require_subscription ?
7002
				sprintf( __fs( 'no-commitment-for-x-days' ), $paid_plan->trial_period ) :
7003
				__fs( 'no-cc-required' ) . '!';
7004
7005
7006
			$total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 );
7007
7008
			if ( $total_paid_plans === $trial_plans_count ) {
7009
				// All paid plans have trials.
7010
				$message = sprintf(
7011
					__fs( 'hey' ) . '! ' . __fs( 'trial-x-promotion-message' ),
7012
					sprintf( '<b>%s</b>', $this->get_plugin_name() ),
7013
					strtolower( __fs( 'awesome' ) ),
7014
					$paid_plan->trial_period
7015
				);
7016
			} else {
7017
				$plans_string = '';
7018
				for ( $i = 0; $i < $trial_plans_count; $i ++ ) {
7019
					$plans_string .= sprintf( '<a href="%s">%s</a>', $upgrade_url, $trial_plans[ $i ]->title );
7020
7021
					if ( $i < $trial_plans_count - 2 ) {
7022
						$plans_string .= ', ';
7023
					} else if ( $i == $trial_plans_count - 2 ) {
7024
						$plans_string .= ' and ';
7025
					}
7026
				}
7027
7028
				// Not all paid plans have trials.
7029
				$message = sprintf(
7030
					__fs( 'hey' ) . '! ' . __fs( 'trial-x-promotion-message' ),
7031
					sprintf( '<b>%s</b>', $this->get_plugin_name() ),
7032
					$plans_string,
7033
					$paid_plan->trial_period
7034
				);
7035
			}
7036
7037
			$message .= ' ' . $cc_string;
7038
7039
			// Add start trial button.
7040
			$message .= ' ' . sprintf(
7041
					'<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
7042
					$upgrade_url,
7043
					__fs( 'start-free-trial' )
7044
				);
7045
7046
			$this->_admin_notices->add_sticky(
7047
				$this->apply_filters( 'trial_promotion_message', $message ),
7048
				'trial_promotion',
7049
				'',
7050
				'promotion'
7051
			);
7052
7053
			$this->_storage->trial_promotion_shown = WP_FS__SCRIPT_START_TIME;
0 ignored issues
show
Documentation introduced by
The property trial_promotion_shown does not exist on object<FS_Key_Value_Storage>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
7054
		}
7055
7056
		/* Action Links
7057
		------------------------------------------------------------------------------------------------------------------*/
7058
		private $_action_links_hooked = false;
7059
		private $_action_links = array();
7060
7061
		/**
7062
		 * @author Vova Feldman (@svovaf)
7063
		 * @since  1.0.0
7064
		 *
7065
		 * @return bool
7066
		 */
7067
		private function is_plugin_action_links_hooked() {
7068
			$this->_logger->entrance( json_encode( $this->_action_links_hooked ) );
7069
7070
			return $this->_action_links_hooked;
7071
		}
7072
7073
		/**
7074
		 * Hook to plugin action links filter.
7075
		 *
7076
		 * @author Vova Feldman (@svovaf)
7077
		 * @since  1.0.0
7078
		 */
7079
		private function hook_plugin_action_links() {
7080
			$this->_logger->entrance();
7081
7082
			$this->_action_links_hooked = true;
7083
7084
			$this->_logger->log( 'Adding action links hooks.' );
7085
7086
			// Add action link to settings page.
7087
			add_filter( 'plugin_action_links_' . $this->_plugin_basename, array(
7088
				&$this,
7089
				'_modify_plugin_action_links_hook'
7090
			), 10, 2 );
7091
			add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array(
7092
				&$this,
7093
				'_modify_plugin_action_links_hook'
7094
			), 10, 2 );
7095
		}
7096
7097
		/**
7098
		 * Add plugin action link.
7099
		 *
7100
		 * @author Vova Feldman (@svovaf)
7101
		 * @since  1.0.0
7102
		 *
7103
		 * @param      $label
7104
		 * @param      $url
7105
		 * @param bool $external
7106
		 * @param int  $priority
7107
		 * @param bool $key
7108
		 */
7109
		function add_plugin_action_link( $label, $url, $external = false, $priority = 10, $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...
7110
			$this->_logger->entrance();
7111
7112
			if ( ! isset( $this->_action_links[ $priority ] ) ) {
7113
				$this->_action_links[ $priority ] = array();
7114
			}
7115
7116
			if ( false === $key ) {
7117
				$key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) );
7118
			}
7119
7120
			$this->_action_links[ $priority ][] = array(
7121
				'label'    => $label,
7122
				'href'     => $url,
7123
				'key'      => $key,
7124
				'external' => $external
7125
			);
7126
		}
7127
7128
		/**
7129
		 * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection.
7130
		 *
7131
		 * @author Vova Feldman (@svovaf)
7132
		 * @since  1.0.0
7133
		 */
7134
		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...
7135
			$this->_logger->entrance();
7136
7137
			if ( $this->is_registered() ) {
7138
				if ( ! $this->is_paying() && $this->has_paid_plan() ) {
7139
					$this->add_plugin_action_link(
7140
						__fs( 'upgrade' ),
7141
						$this->get_upgrade_url(),
7142
						false,
7143
						20,
7144
						'upgrade'
7145
					);
7146
				}
7147
7148
				if ( $this->_has_addons() ) {
7149
					$this->add_plugin_action_link(
7150
						__fs( 'add-ons' ),
7151
						$this->_get_admin_page_url( 'addons' ),
7152
						false,
7153
						10,
7154
						'addons'
7155
					);
7156
				}
7157
			}
7158
		}
7159
7160
		/**
7161
		 * Forward page to activation page.
7162
		 *
7163
		 * @author Vova Feldman (@svovaf)
7164
		 * @since  1.0.3
7165
		 */
7166
		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...
7167
			$url       = false;
7168
			$plugin_fs = false;
7169
7170
			if ( ! $this->is_addon() ) {
7171
				$first_time_path = $this->_menu->get_first_time_path();
7172
				$plugin_fs       = $this;
7173
				$url             = $plugin_fs->is_activation_mode() ?
7174
					$plugin_fs->get_activation_url() :
7175
					( empty( $first_time_path ) ?
7176
						$this->_get_admin_page_url() :
7177
						$first_time_path );
7178
			} else {
7179
				if ( $this->is_parent_plugin_installed() ) {
7180
					$plugin_fs = self::get_parent_instance();
7181
				}
7182
7183
				if ( is_object( $plugin_fs ) ) {
7184
					if ( ! $plugin_fs->is_registered() ) {
7185
						// Forward to parent plugin connect when parent not registered.
7186
						$url = $plugin_fs->get_activation_url();
7187
					} else {
7188
						// Forward to account page.
7189
						$url = $plugin_fs->_get_admin_page_url( 'account' );
7190
					}
7191
				}
7192
			}
7193
7194
			if ( is_string( $url ) ) {
7195
				fs_redirect( $url );
7196
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _redirect_on_activation_hook() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
7197
			}
7198
		}
7199
7200
		/**
7201
		 * Modify plugin's page action links collection.
7202
		 *
7203
		 * @author Vova Feldman (@svovaf)
7204
		 * @since  1.0.0
7205
		 *
7206
		 * @param array $links
7207
		 * @param       $file
7208
		 *
7209
		 * @return array
7210
		 */
7211
		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...
7212
			$this->_logger->entrance();
7213
7214
			ksort( $this->_action_links );
7215
7216
			foreach ( $this->_action_links as $new_links ) {
7217
				foreach ( $new_links as $link ) {
7218
					$links[ $link['key'] ] = '<a href="' . $link['href'] . '"' . ( $link['external'] ? ' target="_blank"' : '' ) . '>' . $link['label'] . '</a>';
7219
				}
7220
			}
7221
7222
			/*
7223
			 * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link.
7224
			 * 
7225
			 * If user is paying or in trial and have the free version installed,
7226
			 * assume that the deactivation is for the upgrade process, so this is not needed.
7227
			 */
7228
			if ( ! $this->is_paying_or_trial() || $this->is_premium() ) {
7229
				if ( isset( $links['deactivate'] ) ) {
7230
					$links['deactivate'] .= '<i class="fs-slug" data-slug="' . $this->_slug . '"></i>';
7231
				}
7232
			}
7233
7234
			return $links;
7235
		}
7236
7237
		/**
7238
		 * Adds admin message.
7239
		 *
7240
		 * @author Vova Feldman (@svovaf)
7241
		 * @since  1.0.4
7242
		 *
7243
		 * @param string $message
7244
		 * @param string $title
7245
		 * @param string $type
7246
		 */
7247
		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...
7248
			$this->_admin_notices->add( $message, $title, $type );
7249
		}
7250
7251
		/**
7252
		 * Adds sticky admin message.
7253
		 *
7254
		 * @author Vova Feldman (@svovaf)
7255
		 * @since  1.1.0
7256
		 *
7257
		 * @param string $message
7258
		 * @param string $id
7259
		 * @param string $title
7260
		 * @param string $type
7261
		 */
7262
		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...
7263
			$this->_admin_notices->add_sticky( $message, $id, $title, $type );
7264
		}
7265
7266
		/* Plugin Auto-Updates (@since 1.0.4)
7267
		------------------------------------------------------------------------------------------------------------------*/
7268
		/**
7269
		 * @var string[]
7270
		 */
7271
		private static $_auto_updated_plugins;
7272
7273
		/**
7274
		 * @todo   TEST IF IT WORKS!!!
7275
		 *
7276
		 * Include plugins for automatic updates based on stored settings.
7277
		 *
7278
		 * @see    http://wordpress.stackexchange.com/questions/131394/how-do-i-exclude-plugins-from-getting-automatically-updated/131404#131404
7279
		 *
7280
		 * @author Vova Feldman (@svovaf)
7281
		 * @since  1.0.4
7282
		 *
7283
		 * @param bool   $update Whether to update (not used for plugins)
7284
		 * @param object $item   The plugin's info
7285
		 *
7286
		 * @return bool
7287
		 */
7288
		static function _include_plugins_in_auto_update( $update, $item ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7289
			// Before version 3.8.2 the $item was the file name of the plugin,
7290
			// while in 3.8.2 statistics were added (https://core.trac.wordpress.org/changeset/27905).
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...
7291
			$by_slug = ( (int) str_replace( '.', '', get_bloginfo( 'version' ) ) >= 382 );
7292
7293
			if ( ! isset( self::$_auto_updated_plugins ) ) {
7294
				$plugins = self::$_accounts->get_option( 'plugins', array() );
7295
7296
				$identifiers = array();
7297
				foreach ( $plugins as $p ) {
7298
					/**
7299
					 * @var FS_Plugin $p
7300
					 */
7301
					if ( isset( $p->auto_update ) && $p->auto_update ) {
7302
						$identifiers[] = ( $by_slug ? $p->slug : plugin_basename( $p->file ) );
7303
					}
7304
				}
7305
7306
				self::$_auto_updated_plugins = $identifiers;
7307
			}
7308
7309
			if ( in_array( $by_slug ? $item->slug : $item, self::$_auto_updated_plugins ) ) {
7310
				return true;
7311
			}
7312
7313
			// Pass update decision to next filters
7314
			return $update;
7315
		}
7316
7317
		#region Versioning ------------------------------------------------------------------
7318
7319
		/**
7320
		 * Check if Freemius in SDK upgrade mode.
7321
		 *
7322
		 * @author Vova Feldman (@svovaf)
7323
		 * @since  1.0.9
7324
		 *
7325
		 * @return bool
7326
		 */
7327
		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...
7328
			return isset( $this->_storage->sdk_upgrade_mode ) ?
7329
				$this->_storage->sdk_upgrade_mode :
0 ignored issues
show
Documentation introduced by
The property sdk_upgrade_mode 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...
7330
				false;
7331
		}
7332
7333
		/**
7334
		 * Turn SDK upgrade mode off.
7335
		 *
7336
		 * @author Vova Feldman (@svovaf)
7337
		 * @since  1.0.9
7338
		 *
7339
		 * @return bool
7340
		 */
7341
		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...
7342
			$this->_storage->sdk_upgrade_mode = false;
0 ignored issues
show
Documentation introduced by
The property sdk_upgrade_mode 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...
7343
		}
7344
7345
		/**
7346
		 * Check if plugin upgrade mode.
7347
		 *
7348
		 * @author Vova Feldman (@svovaf)
7349
		 * @since  1.0.9
7350
		 *
7351
		 * @return bool
7352
		 */
7353
		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...
7354
			return isset( $this->_storage->plugin_upgrade_mode ) ?
7355
				$this->_storage->plugin_upgrade_mode :
0 ignored issues
show
Documentation introduced by
The property plugin_upgrade_mode 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...
7356
				false;
7357
		}
7358
7359
		/**
7360
		 * Turn plugin upgrade mode off.
7361
		 *
7362
		 * @author Vova Feldman (@svovaf)
7363
		 * @since  1.0.9
7364
		 *
7365
		 * @return bool
7366
		 */
7367
		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...
7368
			$this->_storage->plugin_upgrade_mode = false;
0 ignored issues
show
Documentation introduced by
The property plugin_upgrade_mode 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...
7369
		}
7370
7371
		#endregion ------------------------------------------------------------------
7372
7373
		#region Marketing ------------------------------------------------------------------
7374
7375
		/**
7376
		 * Check if current user purchased any other plugins before.
7377
		 *
7378
		 * @author Vova Feldman (@svovaf)
7379
		 * @since  1.0.9
7380
		 *
7381
		 * @return bool
7382
		 */
7383
		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...
7384
			// TODO: Implement has_purchased_before() method.
7385
		}
7386
7387
		/**
7388
		 * Check if current user classified as an agency.
7389
		 *
7390
		 * @author Vova Feldman (@svovaf)
7391
		 * @since  1.0.9
7392
		 *
7393
		 * @return bool
7394
		 */
7395
		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...
7396
			// TODO: Implement is_agency() method.
7397
		}
7398
7399
		/**
7400
		 * Check if current user classified as a developer.
7401
		 *
7402
		 * @author Vova Feldman (@svovaf)
7403
		 * @since  1.0.9
7404
		 *
7405
		 * @return bool
7406
		 */
7407
		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...
7408
			// TODO: Implement is_developer() method.
7409
		}
7410
7411
		/**
7412
		 * Check if current user classified as a business.
7413
		 *
7414
		 * @author Vova Feldman (@svovaf)
7415
		 * @since  1.0.9
7416
		 *
7417
		 * @return bool
7418
		 */
7419
		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...
7420
			// TODO: Implement is_business() method.
7421
		}
7422
7423
		#endregion ------------------------------------------------------------------
7424
	}