Completed
Push — master ( 573ba8...3e9285 )
by Ahmad
09:51
created

Freemius::_uninstall_plugin_hook()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 1
Metric Value
c 5
b 0
f 1
dl 0
loc 32
rs 8.439
nc 4
cc 5
eloc 16
nop 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 13 and the first side effect is on line 9.

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

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

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

Loading history...
2
	/**
3
	 * @package     Freemius
4
	 * @copyright   Copyright (c) 2015, Freemius, Inc.
5
	 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
6
	 * @since       1.0.3
7
	 */
8
	if ( ! defined( 'ABSPATH' ) ) {
9
		exit;
10
	}
11
12
	// "final class"
13
	class Freemius extends Freemius_Abstract {
14
		/**
15
		 * SDK Version
16
		 *
17
		 * @var string
18
		 */
19
		public $version = WP_FS__SDK_VERSION;
20
21
		#region Plugin Info
22
23
		/**
24
		 * @since 1.0.1
25
		 *
26
		 * @var string
27
		 */
28
		private $_slug;
29
30
		/**
31
		 * @since 1.0.0
32
		 *
33
		 * @var string
34
		 */
35
		private $_plugin_basename;
36
		/**
37
		 * @since 1.0.0
38
		 *
39
		 * @var string
40
		 */
41
		private $_free_plugin_basename;
42
		/**
43
		 * @since 1.0.0
44
		 *
45
		 * @var string
46
		 */
47
		private $_plugin_dir_path;
48
		/**
49
		 * @since 1.0.0
50
		 *
51
		 * @var string
52
		 */
53
		private $_plugin_dir_name;
54
		/**
55
		 * @since 1.0.0
56
		 *
57
		 * @var string
58
		 */
59
		private $_plugin_main_file_path;
60
		/**
61
		 * @var string[]
62
		 */
63
		private $_plugin_data;
64
		/**
65
		 * @since 1.0.9
66
		 *
67
		 * @var string
68
		 */
69
		private $_plugin_name;
70
71
		#endregion Plugin Info
72
73
		/**
74
		 * @since 1.0.9
75
		 *
76
		 * @var bool If false, don't turn Freemius on.
77
		 */
78
		private $_is_on;
79
80
		/**
81
		 * @since 1.1.3
82
		 *
83
		 * @var bool If false, don't turn Freemius on.
84
		 */
85
		private $_is_anonymous;
86
87
		/**
88
		 * @since 1.0.9
89
		 * @var bool If false, issues with connectivity to Freemius API.
90
		 */
91
		private $_has_api_connection;
92
93
		/**
94
		 * @since 1.0.9
95
		 * @var bool Hints the SDK if plugin can support anonymous mode (if skip connect is visible).
96
		 */
97
		private $_enable_anonymous;
98
99
		/**
100
		 * @since 1.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
		 * @since 1.1.6
119
		 * @var string[]bool.
120
		 */
121
		private $_permissions;
122
123
		/**
124
		 * @var FS_Key_Value_Storage
125
		 */
126
		private $_storage;
127
128
		/**
129
		 * @since 1.0.0
130
		 *
131
		 * @var FS_Logger
132
		 */
133
		private $_logger;
134
		/**
135
		 * @since 1.0.4
136
		 *
137
		 * @var FS_Plugin
138
		 */
139
		private $_plugin = false;
140
		/**
141
		 * @since 1.0.4
142
		 *
143
		 * @var FS_Plugin
144
		 */
145
		private $_parent_plugin = false;
146
		/**
147
		 * @since 1.1.1
148
		 *
149
		 * @var Freemius
150
		 */
151
		private $_parent = false;
152
		/**
153
		 * @since 1.0.1
154
		 *
155
		 * @var FS_User
156
		 */
157
		private $_user = false;
158
		/**
159
		 * @since 1.0.1
160
		 *
161
		 * @var FS_Site
162
		 */
163
		private $_site = false;
164
		/**
165
		 * @since 1.0.1
166
		 *
167
		 * @var FS_Plugin_License
168
		 */
169
		private $_license;
170
		/**
171
		 * @since 1.0.2
172
		 *
173
		 * @var FS_Plugin_Plan[]
174
		 */
175
		private $_plans = false;
176
		/**
177
		 * @var FS_Plugin_License[]
178
		 * @since 1.0.5
179
		 */
180
		private $_licenses = false;
181
182
		/**
183
		 * @since 1.0.1
184
		 *
185
		 * @var FS_Admin_Menu_Manager
186
		 */
187
		private $_menu;
188
189
		/**
190
		 * @var FS_Admin_Notice_Manager
191
		 */
192
		private $_admin_notices;
193
194
		/**
195
		 * @since 1.1.6
196
		 *
197
		 * @var FS_Admin_Notice_Manager
198
		 */
199
		private static $_global_admin_notices;
200
201
		/**
202
		 * @var FS_Logger
203
		 * @since 1.0.0
204
		 */
205
		private static $_static_logger;
206
207
		/**
208
		 * @var FS_Option_Manager
209
		 * @since 1.0.2
210
		 */
211
		private static $_accounts;
212
213
		/**
214
		 * @var Freemius[]
215
		 */
216
		private static $_instances = array();
217
218
219
		/* Ctor
220
------------------------------------------------------------------------------------------------------------------*/
221
222
		private function __construct( $slug ) {
223
			$this->_slug = $slug;
224
225
			$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $slug, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
226
227
			$this->_storage = FS_Key_Value_Storage::instance( 'plugin_data', $this->_slug );
228
229
			$this->_plugin_main_file_path = $this->_find_caller_plugin_file();
230
			$this->_plugin_dir_path       = plugin_dir_path( $this->_plugin_main_file_path );
231
			$this->_plugin_basename       = plugin_basename( $this->_plugin_main_file_path );
232
			$this->_free_plugin_basename  = str_replace( '-premium/', '/', $this->_plugin_basename );
233
234
			$base_name_split        = explode( '/', $this->_plugin_basename );
235
			$this->_plugin_dir_name = $base_name_split[0];
236
237
			if ( $this->_logger->is_on() ) {
238
				$this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path );
239
				$this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path );
240
				$this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename );
241
				$this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename );
242
				$this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name );
243
			}
244
245
			// Remember link between file to slug.
246
			$this->store_file_slug_map();
247
248
			// Store plugin's initial install timestamp.
249
			if ( ! isset( $this->_storage->install_timestamp ) ) {
250
				$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...
251
			}
252
253
			$this->_plugin = FS_Plugin_Manager::instance( $this->_slug )->get();
254
255
			$this->_admin_notices = FS_Admin_Notice_Manager::instance(
256
				$slug,
257
				is_object( $this->_plugin ) ? $this->_plugin->title : ''
258
			);
259
260
			if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) ||
261
			     'true' === fs_request_is_action( 'restart_freemius' )
262
			) {
263
				FS_Api::clear_cache();
264
			}
265
266
			$this->_register_hooks();
267
268
			$this->_load_account();
269
270
			$this->_version_updates_handler();
271
		}
272
273
		/**
274
		 * @author Vova Feldman (@svovaf)
275
		 * @since  1.0.9
276
		 */
277
		private function _version_updates_handler() {
278
			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...
279
				// Freemius version upgrade mode.
280
				$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...
281
				$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...
282
283
				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...
284
				     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...
285
				) {
286
					$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...
287
					$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...
288
				} else {
289
					$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...
290
					$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...
291
292
				}
293
294
				$this->do_action( 'sdk_version_update', $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...
295
			}
296
297
			$plugin_version = $this->get_plugin_version();
298
			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...
299
				// Plugin version upgrade mode.
300
				$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...
301
				$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...
302
303
				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...
304
				     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...
305
				) {
306
					$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...
307
					$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...
308
				} else {
309
					$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...
310
					$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...
311
				}
312
313
				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...
314
					// Different version of the plugin was installed before, therefore it's an update.
315
					$this->_storage->is_plugin_new_install = false;
0 ignored issues
show
Documentation introduced by
The property is_plugin_new_install 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...
316
				}
317
318
				$this->do_action( 'plugin_version_update', $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...
319
			}
320
		}
321
322
		/**
323
		 * @author Vova Feldman (@svovaf)
324
		 * @since  1.1.5
325
		 *
326
		 * @param string $sdk_prev_version
327
		 * @param string $sdk_version
328
		 */
329
		function _data_migration( $sdk_prev_version, $sdk_version ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
330
			/**
331
			 * @since 1.1.7.3 Fixed unwanted connectivity test cleanup.
332
			 */
333
			if ( empty( $sdk_prev_version ) ) {
334
				return;
335
			}
336
337
			if ( version_compare( $sdk_prev_version, '1.1.5', '<' ) &&
338
			     version_compare( $sdk_version, '1.1.5', '>=' )
339
			) {
340
				// On version 1.1.5 merged connectivity and is_on data.
341
				if ( isset( $this->_storage->connectivity_test ) ) {
342
					if ( ! isset( $this->_storage->is_on ) ) {
343
						unset( $this->_storage->connectivity_test );
344
					} else {
345
						$connectivity_data              = $this->_storage->connectivity_test;
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...
346
						$connectivity_data['is_active'] = $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...
347
						$connectivity_data['timestamp'] = $this->_storage->is_on['timestamp'];
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...
348
349
						// Override.
350
						$this->_storage->connectivity_test = $connectivity_data;
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...
351
352
						// Remove previous structure.
353
						unset( $this->_storage->is_on );
354
					}
355
356
				}
357
			}
358
		}
359
360
		/**
361
		 * @author Vova Feldman (@svovaf)
362
		 * @since  1.0.9
363
		 */
364
		private function _register_hooks() {
365
			if ( is_admin() ) {
366
				// Hook to plugin activation
367
				register_activation_hook( $this->_plugin_main_file_path, array(
368
					&$this,
369
					'_activate_plugin_event_hook'
370
				) );
371
372
				// Hook to plugin uninstall.
373
				register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) );
374
375
				if ( ! $this->is_ajax() ) {
376
					if ( ! $this->is_addon() ) {
377
						add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY );
378
						add_action( 'admin_menu', array( &$this, '_prepare_admin_menu' ), WP_FS__LOWEST_PRIORITY );
379
					}
380
				}
381
			}
382
383
			register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) );
384
385
			add_action( 'init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY );
386
387
			$this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) );
388
389
			$this->add_action( 'sdk_version_update', array( &$this, '_data_migration' ), WP_FS__DEFAULT_PRIORITY, 2 );
390
		}
391
392
		/**
393
		 * @author Vova Feldman (@svovaf)
394
		 * @since  1.0.9
395
		 */
396
		private function _register_account_hooks() {
397
			if ( is_admin() ) {
398
				if ( ! $this->is_ajax() ) {
399
					if ( $this->has_trial_plan() ) {
400
						$last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false );
401
						if ( ! $this->_site->is_trial_utilized() &&
402
						     (
403
							     // Show promotion if never shown it yet and 24 hours after initial activation.
404
							     ( 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...
405
							     // Show promotion in every 30 days.
406
							     ( is_numeric( $last_time_trial_promotion_shown ) && 30 * WP_FS__TIME_24_HOURS_IN_SEC < time() - $last_time_trial_promotion_shown ) )
407
						) {
408
							$this->add_action( 'after_init_plugin_registered', array( &$this, '_add_trial_notice' ) );
409
						}
410
					}
411
				}
412
413
				// If user is paying or in trial and have the free version installed,
414
				// assume that the deactivation is for the upgrade process.
415
				if ( ! $this->is_paying_or_trial() || $this->is_premium() ) {
416
					add_action( 'wp_ajax_submit-uninstall-reason', array( &$this, '_submit_uninstall_reason_action' ) );
417
418
					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...
419
					if ( 'plugins.php' === $pagenow ) {
420
						add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) );
421
					}
422
				}
423
			}
424
		}
425
426
		/**
427
		 * Leverage backtrace to find caller plugin file path.
428
		 *
429
		 * @author Vova Feldman (@svovaf)
430
		 * @since  1.0.6
431
		 *
432
		 * @return string
433
		 *
434
		 * @uses   fs_find_caller_plugin_file
435
		 */
436
		private function _find_caller_plugin_file() {
437
			// Try to load the cached value of the file path.
438
			if ( isset( $this->_storage->plugin_main_file ) ) {
439
				if ( file_exists( $this->_storage->plugin_main_file->path ) ) {
0 ignored issues
show
Documentation introduced by
The property plugin_main_file 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...
440
					return $this->_storage->plugin_main_file->path;
0 ignored issues
show
Documentation introduced by
The property plugin_main_file 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...
441
				}
442
			}
443
444
			$plugin_file = fs_find_caller_plugin_file();
445
446
			$this->_storage->plugin_main_file = (object) array(
0 ignored issues
show
Documentation introduced by
The property plugin_main_file 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...
447
				'path' => fs_normalize_path( $plugin_file ),
448
			);
449
450
			return $plugin_file;
451
		}
452
453
454
		#region Deactivation Feedback Form ------------------------------------------------------------------
455
456
		/**
457
		 * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins
458
		 * page.
459
		 *
460
		 * @author Vova Feldman (@svovaf)
461
		 * @author Leo Fajardo (@leorw)
462
		 * @since  1.1.2
463
		 */
464
		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...
465
			fs_enqueue_local_style( 'fs_deactivation_feedback', '/admin/deactivation-feedback.css' );
466
467
			/* Check the type of user:
468
			 * 1. Long-term (long-term)
469
			 * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term).
470
			 * 3. Short-term (short-term)
471
			 */
472
			$is_long_term_user = true;
473
474
			// Check if the site is at least 2 days old.
475
			$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...
476
477
			// Difference in seconds.
478
			$date_diff = time() - $time_installed;
479
480
			// Convert seconds to days.
481
			$date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) );
482
483
			if ( $date_diff_days < 2 ) {
484
				$is_long_term_user = false;
485
			}
486
487
			$is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user );
488
489
			if ( $is_long_term_user ) {
490
				$user_type = 'long-term';
491
			} else {
492
				if ( ! $this->is_registered() && ! $this->is_anonymous() ) {
493
					$user_type = 'non-registered-and-non-anonymous-short-term';
494
				} else {
495
					$user_type = 'short-term';
496
				}
497
			}
498
499
			$uninstall_reasons = $this->_get_uninstall_reasons( $user_type );
500
501
			// Load the HTML template for the deactivation feedback dialog box.
502
			$vars = array(
503
				'reasons' => $uninstall_reasons,
504
				'slug'    => $this->_slug
505
			);
506
507
			/**
508
			 * @todo Deactivation form core functions should be loaded only once! Otherwise, when there are multiple Freemius powered plugins the same code is loaded multiple times. The only thing that should be loaded differently is the various deactivation reasons object based on the state of the plugin.
509
			 */
510
			fs_require_template( 'deactivation-feedback-modal.php', $vars );
511
		}
512
513
		/**
514
		 * @author Leo Fajardo (leorw)
515
		 * @since  1.1.2
516
		 *
517
		 * @param string $user_type
518
		 *
519
		 * @return array The uninstall reasons for the specified user type.
520
		 */
521
		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...
522
			$reason_found_better_plugin = array(
523
				'id'                => 2,
524
				'text'              => __fs( 'reason-found-a-better-plugin', $this->_slug ),
525
				'input_type'        => 'textfield',
526
				'input_placeholder' => __fs( 'placeholder-plugin-name', $this->_slug )
527
			);
528
529
			$reason_other = array(
530
				'id'                => 7,
531
				'text'              => __fs( 'reason-other', $this->_slug ),
532
				'input_type'        => 'textfield',
533
				'input_placeholder' => ''
534
			);
535
536
			$long_term_user_reasons = array(
537
				array(
538
					'id'                => 1,
539
					'text'              => __fs( 'reason-no-longer-needed', $this->_slug ),
540
					'input_type'        => '',
541
					'input_placeholder' => ''
542
				),
543
				$reason_found_better_plugin,
544
				array(
545
					'id'                => 3,
546
					'text'              => __fs( 'reason-needed-for-a-short-period', $this->_slug ),
547
					'input_type'        => '',
548
					'input_placeholder' => ''
549
				),
550
				array(
551
					'id'                => 4,
552
					'text'              => __fs( 'reason-broke-my-site', $this->_slug ),
553
					'input_type'        => '',
554
					'input_placeholder' => ''
555
				),
556
				array(
557
					'id'                => 5,
558
					'text'              => __fs( 'reason-suddenly-stopped-working', $this->_slug ),
559
					'input_type'        => '',
560
					'input_placeholder' => ''
561
				)
562
			);
563
564
			if ( $this->is_paying() ) {
565
				$long_term_user_reasons[] = array(
566
					'id'                => 6,
567
					'text'              => __fs( 'reason-cant-pay-anymore', $this->_slug ),
568
					'input_type'        => 'textfield',
569
					'input_placeholder' => __fs( 'placeholder-comfortable-price', $this->_slug )
570
				);
571
			}
572
573
			$long_term_user_reasons[] = $reason_other;
574
575
			$uninstall_reasons = array(
576
				'long-term'                                   => $long_term_user_reasons,
577
				'non-registered-and-non-anonymous-short-term' => array(
578
					array(
579
						'id'                => 8,
580
						'text'              => __fs( 'reason-didnt-work', $this->_slug ),
581
						'input_type'        => '',
582
						'input_placeholder' => ''
583
					),
584
					array(
585
						'id'                => 9,
586
						'text'              => __fs( 'reason-dont-like-to-share-my-information', $this->_slug ),
587
						'input_type'        => '',
588
						'input_placeholder' => ''
589
					),
590
					$reason_found_better_plugin,
591
					$reason_other
592
				),
593
				'short-term'                                  => array(
594
					array(
595
						'id'                => 10,
596
						'text'              => __fs( 'reason-couldnt-make-it-work', $this->_slug ),
597
						'input_type'        => '',
598
						'input_placeholder' => ''
599
					),
600
					$reason_found_better_plugin,
601
					array(
602
						'id'                => 11,
603
						'text'              => __fs( 'reason-great-but-need-specific-feature', $this->_slug ),
604
						'input_type'        => 'textarea',
605
						'input_placeholder' => __fs( 'placeholder-feature', $this->_slug )
606
					),
607
					array(
608
						'id'                => 12,
609
						'text'              => __fs( 'reason-not-working', $this->_slug ),
610
						'input_type'        => 'textarea',
611
						'input_placeholder' => __fs( 'placeholder-share-what-didnt-work', $this->_slug )
612
					),
613
					array(
614
						'id'                => 13,
615
						'text'              => __fs( 'reason-not-what-i-was-looking-for', $this->_slug ),
616
						'input_type'        => 'textarea',
617
						'input_placeholder' => __fs( 'placeholder-what-youve-been-looking-for', $this->_slug )
618
					),
619
					array(
620
						'id'                => 14,
621
						'text'              => __fs( 'reason-didnt-work-as-expected', $this->_slug ),
622
						'input_type'        => 'textarea',
623
						'input_placeholder' => __fs( 'placeholder-what-did-you-expect', $this->_slug )
624
					),
625
					$reason_other
626
				)
627
			);
628
629
			$uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons );
630
631
			return $uninstall_reasons[ $user_type ];
632
		}
633
634
		/**
635
		 * Called after the user has submitted his reason for deactivating the plugin.
636
		 *
637
		 * @author Leo Fajardo (@leorw)
638
		 * @since  1.1.2
639
		 */
640
		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...
641
			if ( ! isset( $_POST['reason_id'] ) ) {
642
				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...
643
			}
644
645
			$reason_info = isset( $_REQUEST['reason_info'] ) ? trim( stripslashes( $_REQUEST['reason_info'] ) ) : '';
646
647
			$reason = (object) array(
648
				'id'   => $_POST['reason_id'],
649
				'info' => substr( $reason_info, 0, 128 )
650
			);
651
652
			$this->_storage->store( 'uninstall_reason', $reason );
653
654
			// Print '1' for successful operation.
655
			echo 1;
656
			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...
657
		}
658
659
		#endregion Deactivation Feedback Form ------------------------------------------------------------------
660
661
		#region Instance ------------------------------------------------------------------
662
663
		/**
664
		 * Main singleton instance.
665
		 *
666
		 * @author Vova Feldman (@svovaf)
667
		 * @since  1.0.0
668
		 *
669
		 * @param $slug
670
		 *
671
		 * @return Freemius
672
		 */
673
		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...
674
			$slug = strtolower( $slug );
675
676
			if ( ! isset( self::$_instances[ $slug ] ) ) {
677
				if ( 0 === count( self::$_instances ) ) {
678
					self::_load_required_static();
679
				}
680
681
				self::$_instances[ $slug ] = new Freemius( $slug );
682
			}
683
684
			return self::$_instances[ $slug ];
685
		}
686
687
		/**
688
		 * @author Vova Feldman (@svovaf)
689
		 * @since  1.0.6
690
		 *
691
		 * @param string|number $slug_or_id
692
		 *
693
		 * @return bool
694
		 */
695
		private static function has_instance( $slug_or_id ) {
696
			return ! is_numeric( $slug_or_id ) ?
697
				isset( self::$_instances[ strtolower( $slug_or_id ) ] ) :
698
				( false !== self::get_instance_by_id( $slug_or_id ) );
699
		}
700
701
		/**
702
		 * @author Vova Feldman (@svovaf)
703
		 * @since  1.0.6
704
		 *
705
		 * @param $id
706
		 *
707
		 * @return false|Freemius
708
		 */
709
		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...
710
			foreach ( self::$_instances as $slug => $instance ) {
711
				if ( $id == $instance->get_id() ) {
712
					return $instance;
713
				}
714
			}
715
716
			return false;
717
		}
718
719
		/**
720
		 *
721
		 * @author Vova Feldman (@svovaf)
722
		 * @since  1.0.1
723
		 *
724
		 * @param $plugin_file
725
		 *
726
		 * @return false|Freemius
727
		 */
728
		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...
729
			$slug = self::find_slug_by_basename( $plugin_file );
730
731
			return ( false !== $slug ) ?
732
				self::instance( $slug ) :
733
				false;
734
		}
735
736
		/**
737
		 * @author Vova Feldman (@svovaf)
738
		 * @since  1.0.6
739
		 *
740
		 * @return false|Freemius
741
		 */
742
		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...
743
			return self::get_instance_by_id( $this->_plugin->parent_plugin_id );
744
		}
745
746
		/**
747
		 * @author Vova Feldman (@svovaf)
748
		 * @since  1.0.6
749
		 *
750
		 * @param $slug_or_id
751
		 *
752
		 * @return bool|Freemius
753
		 */
754
		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...
755
			return ! is_numeric( $slug_or_id ) ?
756
				self::instance( strtolower( $slug_or_id ) ) :
757
				self::get_instance_by_id( $slug_or_id );
758
		}
759
760
		#endregion ------------------------------------------------------------------
761
762
		/**
763
		 * @author Vova Feldman (@svovaf)
764
		 * @since  1.0.6
765
		 *
766
		 * @return bool
767
		 */
768
		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...
769
			return self::has_instance( $this->_plugin->parent_plugin_id );
770
		}
771
772
		/**
773
		 * Check if add-on parent plugin in activation mode.
774
		 *
775
		 * @author Vova Feldman (@svovaf)
776
		 * @since  1.0.7
777
		 *
778
		 * @return bool
779
		 */
780
		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...
781
			$parent_fs = $this->get_parent_instance();
782
			if ( ! is_object( $parent_fs ) ) {
783
				return false;
784
			}
785
786
			return ( $parent_fs->is_activation_mode() );
787
		}
788
789
		/**
790
		 * Is plugin in activation mode.
791
		 *
792
		 * @author Vova Feldman (@svovaf)
793
		 * @since  1.0.7
794
		 *
795
		 * @return bool
796
		 */
797
		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...
798
			return (
799
				! $this->is_registered() &&
800
				( ! $this->enable_anonymous() ||
801
				  ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) )
802
			);
803
		}
804
805
		/**
806
		 * Get collection of all active plugins.
807
		 *
808
		 * @author Vova Feldman (@svovaf)
809
		 * @since  1.0.9
810
		 *
811
		 * @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...
812
		 */
813
		private function get_active_plugins() {
814
			self::require_plugin_essentials();
815
816
			$active_plugin            = array();
817
			$all_plugins              = get_plugins();
818
			$active_plugins_basenames = get_option( 'active_plugins' );
819
820
			foreach ( $active_plugins_basenames as $plugin_basename ) {
821
				$active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ];
822
			}
823
824
			return $active_plugin;
825
		}
826
827
		private static $_statics_loaded = false;
828
829
		/**
830
		 * Load static resources.
831
		 *
832
		 * @author Vova Feldman (@svovaf)
833
		 * @since  1.0.1
834
		 */
835
		private static function _load_required_static() {
836
			if ( self::$_statics_loaded ) {
837
				return;
838
			}
839
840
			self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
841
842
			self::$_static_logger->entrance();
843
844
			self::$_accounts = FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true );
845
846
			self::$_global_admin_notices = FS_Admin_Notice_Manager::instance( 'global' );
847
848
			// Configure which Freemius powered plugins should be auto updated.
849
//			add_filter( 'auto_update_plugin', '_include_plugins_in_auto_update', 10, 2 );
850
851
			add_action( 'admin_menu', array( 'Freemius', 'add_debug_page' ) );
852
853
			add_action( "wp_ajax_fs_toggle_debug_mode", array( 'Freemius', '_toggle_debug_mode' ) );
854
855
			self::$_statics_loaded = true;
856
		}
857
858
		#region Debugging ------------------------------------------------------------------
859
860
		/**
861
		 * @author Vova Feldman (@svovaf)
862
		 * @since  1.0.8
863
		 */
864
		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...
865
			if ( ! current_user_can( 'activate_plugins' ) ) {
866
				return;
867
			}
868
869
			self::$_static_logger->entrance();
870
871
			$title = sprintf( '%s [v.%s]', __fs( 'freemius-debug' ), WP_FS__SDK_VERSION );
872
873
			if ( WP_FS__DEV_MODE ) {
874
				// Add top-level debug menu item.
875
				$hook = add_object_page(
876
					$title,
877
					$title,
878
					'manage_options',
879
					'freemius',
880
					array( 'Freemius', '_debug_page_render' )
881
				);
882
			} else {
883
				// Add hidden debug page.
884
				$hook = add_submenu_page(
885
					null,
886
					$title,
887
					$title,
888
					'manage_options',
889
					'freemius',
890
					array( 'Freemius', '_debug_page_render' )
891
				);
892
			}
893
894
			add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) );
895
		}
896
897
		/**
898
		 * @author Vova Feldman (@svovaf)
899
		 * @since  1.1.7.3
900
		 */
901
		static function _toggle_debug_mode() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
902
			if ( in_array( $_POST['is_on'], array( 0, 1 ) ) ) {
903
				update_option( 'fs_debug_mode', $_POST['is_on'] );
904
			}
905
906
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method _toggle_debug_mode() 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...
907
		}
908
909
		/**
910
		 * @author Vova Feldman (@svovaf)
911
		 * @since  1.0.8
912
		 */
913
		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...
914
			self::_clean_admin_content_section();
915
916
			if ( fs_request_is_action( 'restart_freemius' ) ) {
917
				check_admin_referer( 'restart_freemius' );
918
919
				self::$_accounts->clear( true );
920
921
922
				return;
923
			}
924
		}
925
926
		/**
927
		 * @author Vova Feldman (@svovaf)
928
		 * @since  1.0.8
929
		 */
930
		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...
931
			self::$_static_logger->entrance();
932
933
			$sites          = self::get_all_sites();
934
			$users          = self::get_all_users();
935
			$addons         = self::get_all_addons();
936
			$account_addons = self::get_all_account_addons();
937
938
//			$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...
939
//			$licenses = self::get_all_licenses();
940
941
			$vars = array(
942
				'sites'          => $sites,
943
				'users'          => $users,
944
				'addons'         => $addons,
945
				'account_addons' => $account_addons,
946
			);
947
948
			fs_enqueue_local_style( 'fs_account', '/admin/debug.css' );
949
			fs_require_once_template( 'debug.php', $vars );
950
		}
951
952
		#endregion ------------------------------------------------------------------
953
954
		#region Connectivity Issues ------------------------------------------------------------------
955
956
		/**
957
		 * Check if Freemius should be turned on for the current plugin install.
958
		 *
959
		 * Note:
960
		 *  $this->_is_on is updated in has_api_connectivity()
961
		 *
962
		 * @author Vova Feldman (@svovaf)
963
		 * @since  1.0.9
964
		 *
965
		 * @return bool
966
		 */
967
		function is_on() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
968
			self::$_static_logger->entrance();
969
970
			if ( isset( $this->_is_on ) ) {
971
				return $this->_is_on;
972
			}
973
974
			// If already installed or pending then sure it's on :)
975
			if ( $this->is_registered() || $this->is_pending_activation() ) {
976
				$this->_is_on = true;
977
978
				return true;
979
			}
980
981
			return false;
982
		}
983
984
		/**
985
		 * @author Vova Feldman (@svovaf)
986
		 * @since  1.1.7.3
987
		 *
988
		 * @param bool $flush_if_no_connectivity
989
		 *
990
		 * @return bool
991
		 */
992
		private function should_run_connectivity_test( $flush_if_no_connectivity = false ) {
993
			if ( ! isset( $this->_storage->connectivity_test ) ) {
994
				// Connectivity test was never executed, or cache was cleared.
995
				return true;
996
			}
997
998
			if ( WP_FS__PING_API_ON_IP_OR_HOST_CHANGES ) {
999
				if ( WP_FS__IS_HTTP_REQUEST ) {
1000
					if ( $_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...
1001
						// Domain changed.
1002
						return true;
1003
					}
1004
1005
					if ( WP_FS__REMOTE_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...
1006
						// Server IP changed.
1007
						return true;
1008
					}
1009
				}
1010
			}
1011
1012
			if ( $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...
1013
			     $this->_storage->connectivity_test['is_active']
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...
1014
			) {
1015
				// API connected and Freemius is active - no need to run connectivity check.
1016
				return false;
1017
			}
1018
1019
			if ( $flush_if_no_connectivity ) {
1020
				/**
1021
				 * If explicitly asked to flush when no connectivity - do it only
1022
				 * if at least 10 sec passed from the last API connectivity test.
1023
				 */
1024
				return ( isset( $this->_storage->connectivity_test['timestamp'] ) &&
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...
1025
				         ( WP_FS__SCRIPT_START_TIME - $this->_storage->connectivity_test['timestamp'] ) > 10 );
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...
1026
			}
1027
1028
			/**
1029
			 * @since 1.1.7 Don't check for connectivity on plugin downgrade.
1030
			 */
1031
			$version = $this->get_plugin_version();
1032
			if ( version_compare( $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...
1033
				// If it's a plugin version upgrade and Freemius is off or no connectivity, run connectivity test.
1034
				return true;
1035
			}
1036
1037
			return false;
1038
		}
1039
1040
		/**
1041
		 * Check if there's any connectivity issue to Freemius API.
1042
		 *
1043
		 * @author Vova Feldman (@svovaf)
1044
		 * @since  1.0.9
1045
		 *
1046
		 * @param bool $flush_if_no_connectivity
1047
		 *
1048
		 * @return bool
1049
		 */
1050
		function has_api_connectivity( $flush_if_no_connectivity = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
1051
			if ( isset( $this->_has_api_connection ) && ( $this->_has_api_connection || ! $flush_if_no_connectivity ) ) {
1052
				return $this->_has_api_connection;
1053
			}
1054
1055
			$version = $this->get_plugin_version();
1056
1057
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY &&
1058
			     isset( $this->_storage->connectivity_test ) &&
1059
			     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...
1060
			) {
1061
				unset( $this->_storage->connectivity_test );
1062
			}
1063
1064
			if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) {
1065
				$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...
1066
				/**
1067
				 * @since 1.1.6 During dev mode, if there's connectivity - turn Freemius on regardless the configuration.
1068
				 */
1069
				$this->_is_on = $this->_storage->connectivity_test['is_active'] ||
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...
1070
				                ( WP_FS__DEV_MODE && $this->_has_api_connection );
1071
1072
				return $this->_has_api_connection;
1073
			}
1074
1075
			$is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() );
1076
1077
			$pong = null;
1078
1079
			if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) {
1080
				$is_connected = false;
1081
			} else {
1082
				$pong         = $this->get_api_plugin_scope()->ping(
1083
					$this->get_anonymous_id(),
1084
					array(
1085
						'is_update' => json_encode( $is_update ),
1086
						'version'   => $version,
1087
						'sdk'       => $this->version,
1088
						'is_admin'  => json_encode( is_admin() ),
1089
						'is_ajax'   => json_encode( $this->is_ajax() ),
1090
						'is_cron'   => json_encode( $this->is_cron() ),
1091
						'is_http'   => json_encode( WP_FS__IS_HTTP_REQUEST ),
1092
					)
1093
				);
1094
				$is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong );
1095
			}
1096
1097
			if ( ! $is_connected ) {
1098
				// Another API failure.
1099
				$this->_add_connectivity_issue_message( $pong );
1100
			}
1101
1102
			$is_active = $this->apply_filters(
1103
				'is_on',
1104
				( ! $is_connected ) ? false :
1105
					( isset( $pong->is_active ) && true == $pong->is_active ),
1106
				$this->is_plugin_update(),
1107
				$version
1108
			);
1109
1110
			$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...
1111
				'is_connected' => $is_connected,
1112
				'host'         => $_SERVER['HTTP_HOST'],
1113
				'server_ip'    => WP_FS__REMOTE_ADDR,
1114
				'is_active'    => $is_active,
1115
				'timestamp'    => WP_FS__SCRIPT_START_TIME,
1116
				// Last version with connectivity attempt.
1117
				'version'      => $version,
1118
			);
1119
1120
			$this->_has_api_connection = $is_connected;
1121
			$this->_is_on              = $is_active || ( WP_FS__DEV_MODE && $is_connected );
1122
1123
			return $this->_has_api_connection;
1124
		}
1125
1126
		/**
1127
		 * Anonymous and unique site identifier (Hash).
1128
		 *
1129
		 * @author Vova Feldman (@svovaf)
1130
		 * @since  1.1.0
1131
		 *
1132
		 * @return string
1133
		 */
1134
		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...
1135
			if ( ! self::$_accounts->has_option( 'unique_id' ) ) {
1136
				$key = get_site_url();
1137
1138
				// If localhost, assign microtime instead of domain.
1139
				if ( WP_FS__IS_LOCALHOST || false !== strpos( $key, 'localhost' ) ) {
1140
					$key = microtime();
1141
				}
1142
1143
				self::$_accounts->set_option( 'unique_id', md5( $key ), true );
1144
			}
1145
1146
			return self::$_accounts->get_option( 'unique_id' );
1147
		}
1148
1149
		/**
1150
		 * Generate API connectivity issue message.
1151
		 *
1152
		 * @author Vova Feldman (@svovaf)
1153
		 * @since  1.0.9
1154
		 *
1155
		 * @param mixed $api_result
1156
		 */
1157
		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...
1158
			if ( $this->_enable_anonymous ) {
1159
				// Don't add message if can run anonymously.
1160
				return;
1161
			}
1162
1163
			if ( ! function_exists( 'wp_nonce_url' ) ) {
1164
				require_once( ABSPATH . 'wp-includes/functions.php' );
1165
			}
1166
1167
			self::require_pluggable_essentials();
1168
1169
			$current_user = wp_get_current_user();
1170
//			$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...
1171
			$admin_email = $current_user->user_email;
1172
1173
			$message = false;
1174
			if ( is_object( $api_result ) &&
1175
			     isset( $api_result->error ) &&
1176
			     isset( $api_result->error->code )
1177
			) {
1178
				switch ( $api_result->error->code ) {
1179
					case 'curl_missing':
1180
						$message = sprintf(
1181
							__fs( 'x-requires-access-to-api', $this->_slug ) . ' ' .
1182
							__fs( 'curl-missing-message', $this->_slug ) . ' ' .
1183
							' %s',
1184
							'<b>' . $this->get_plugin_name() . '</b>',
1185
							sprintf(
1186
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
1187
								sprintf(
1188
									'<a class="fs-resolve" data-type="curl" href="#"><b>%s</b></a>%s',
1189
									__fs( 'curl-missing-no-clue-title', $this->_slug ),
1190
									' - ' . sprintf(
1191
										__fs( 'curl-missing-no-clue-desc', $this->_slug ),
1192
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1193
									)
1194
								),
1195
								sprintf(
1196
									'<b>%s</b> - %s',
1197
									__fs( 'sysadmin-title', $this->_slug ),
1198
									__fs( 'curl-missing-sysadmin-desc', $this->_slug )
1199
								),
1200
								sprintf(
1201
									'<a href="%s"><b>%s</b></a>%s',
1202
									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 ),
1203
									__fs( 'deactivate-plugin-title', $this->_slug ),
1204
									' - ' . __fs( 'deactivate-plugin-desc', 'freemius', $this->_slug )
0 ignored issues
show
Unused Code introduced by
The call to __fs() has too many arguments starting with $this->_slug.

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...
1205
								)
1206
							)
1207
						);
1208
						break;
1209
					case 'cloudflare_ddos_protection':
1210
						$message = sprintf(
1211
							__fs( 'x-requires-access-to-api', $this->_slug ) . ' ' .
1212
							__fs( 'cloudflare-blocks-connection-message', $this->_slug ) . ' ' .
1213
							__fs( 'happy-to-resolve-issue-asap', $this->_slug ) .
1214
							' %s',
1215
							'<b>' . $this->get_plugin_name() . '</b>',
1216
							sprintf(
1217
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
1218
								sprintf(
1219
									'<a class="fs-resolve" data-type="cloudflare" href="#"><b>%s</b></a>%s',
1220
									__fs( 'fix-issue-title', $this->_slug ),
1221
									' - ' . sprintf(
1222
										__fs( 'fix-issue-desc', $this->_slug ),
1223
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1224
									)
1225
								),
1226
								sprintf(
1227
									'<a href="%s" target="_blank"><b>%s</b></a>%s',
1228
									sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
1229
									__fs( 'install-previous-title', $this->_slug ),
1230
									' - ' . __fs( 'install-previous-desc', $this->_slug )
1231
								),
1232
								sprintf(
1233
									'<a href="%s"><b>%s</b></a>%s',
1234
									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 ),
1235
									__fs( 'deactivate-plugin-title', $this->_slug ),
1236
									' - ' . __fs( 'deactivate-plugin-desc', $this->_slug )
1237
								)
1238
							)
1239
						);
1240
						break;
1241
					case 'squid_cache_block':
1242
						$message = sprintf(
1243
							__fs( 'x-requires-access-to-api', $this->_slug ) . ' ' .
1244
							__fs( 'squid-blocks-connection-message', $this->_slug ) .
1245
							' %s',
1246
							'<b>' . $this->get_plugin_name() . '</b>',
1247
							sprintf(
1248
								'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
1249
								sprintf(
1250
									'<a class="fs-resolve" data-type="squid" href="#"><b>%s</b></a>%s',
1251
									__fs( 'squid-no-clue-title', $this->_slug ),
1252
									' - ' . sprintf(
1253
										__fs( 'squid-no-clue-desc', $this->_slug ),
1254
										'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1255
									)
1256
								),
1257
								sprintf(
1258
									'<b>%s</b> - %s',
1259
									__fs( 'sysadmin-title', $this->_slug ),
1260
									sprintf(
1261
										__fs( 'squid-sysadmin-desc', $this->_slug ),
1262
										// We use a filter since the plugin might require additional API connectivity.
1263
										'<b>' . implode( ', ', $this->apply_filters( 'api_domains', array( 'api.freemius.com' ) ) ) . '</b>' )
1264
								),
1265
								sprintf(
1266
									'<a href="%s"><b>%s</b></a>%s',
1267
									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 ),
1268
									__fs( 'deactivate-plugin-title', $this->_slug ),
1269
									' - ' . __fs( 'deactivate-plugin-desc', $this->_slug )
1270
								)
1271
							)
1272
						);
1273
						break;
1274
//					default:
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% 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...
1275
//						$message = __fs( 'connectivity-test-fails-message', $this->_slug );
1276
//						break;
1277
				}
1278
			}
1279
1280
			if ( false === $message ) {
1281
				$message = sprintf(
1282
					__fs( 'x-requires-access-to-api', $this->_slug ) . ' ' .
1283
					__fs( 'connectivity-test-fails-message', $this->_slug ) . ' ' .
1284
					__fs( 'happy-to-resolve-issue-asap', $this->_slug ) .
1285
					' %s',
1286
					'<b>' . $this->get_plugin_name() . '</b>',
1287
					sprintf(
1288
						'<ol id="fs_firewall_issue_options"><li>%s</li><li>%s</li><li>%s</li></ol>',
1289
						sprintf(
1290
							'<a class="fs-resolve" data-type="general" href="#"><b>%s</b></a>%s',
1291
							__fs( 'fix-issue-title', $this->_slug ),
1292
							' - ' . sprintf(
1293
								__fs( 'fix-issue-desc', $this->_slug ),
1294
								'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1295
							)
1296
						),
1297
						sprintf(
1298
							'<a href="%s" target="_blank"><b>%s</b></a>%s',
1299
							sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ),
1300
							__fs( 'install-previous-title', $this->_slug ),
1301
							' - ' . __fs( 'install-previous-desc', $this->_slug )
1302
						),
1303
						sprintf(
1304
							'<a href="%s"><b>%s</b></a>%s',
1305
							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 ),
1306
							__fs( 'deactivate-plugin-title', $this->_slug ),
1307
							' - ' . __fs( 'deactivate-plugin-desc', $this->_slug )
1308
						)
1309
					)
1310
				);
1311
			}
1312
1313
			$this->_admin_notices->add_sticky(
1314
				$message,
1315
				'failed_connect_api',
1316
				__fs( 'oops', $this->_slug ) . '...',
1317
				'error'
1318
			);
1319
		}
1320
1321
		/**
1322
		 * Handle user request to resolve connectivity issue.
1323
		 * This method will send an email to Freemius API technical staff for resolution.
1324
		 * The email will contain server's info and installed plugins (might be caching issue).
1325
		 *
1326
		 * @author Vova Feldman (@svovaf)
1327
		 * @since  1.0.9
1328
		 */
1329
		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...
1330
			$this->_admin_notices->remove_sticky( 'failed_connect_api' );
1331
1332
			self::require_pluggable_essentials();
1333
1334
			$current_user = wp_get_current_user();
1335
			$admin_email  = $current_user->user_email;
1336
1337
			$is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() );
1338
1339
			$ping = $this->get_api_plugin_scope()->ping(
1340
				$this->get_anonymous_id(),
1341
				array(
1342
					'is_update' => json_encode( $is_update ),
1343
					'version'   => $this->get_plugin_version(),
1344
					'sdk'       => $this->version,
1345
					'is_admin'  => json_encode( is_admin() ),
1346
					'is_ajax'   => json_encode( $this->is_ajax() ),
1347
					'is_cron'   => json_encode( $this->is_cron() ),
1348
					'is_http'   => json_encode( WP_FS__IS_HTTP_REQUEST ),
1349
				)
1350
			);
1351
1352
			$error_type = fs_request_get( 'error_type', 'general' );
1353
1354
			switch ( $error_type ) {
1355
				case 'squid':
1356
					$title = 'Squid ACL Blocking Issue';
1357
					break;
1358
				case 'cloudflare':
1359
					$title = 'CloudFlare Blocking Issue';
1360
					break;
1361
				default:
1362
					$title = 'API Connectivity Issue';
1363
					break;
1364
			}
1365
1366
			$custom_email_sections = array();
1367
1368
			if ( 'squid' === $error_type ) {
1369
				// Override the 'Site' email section.
1370
				$custom_email_sections['site'] = array(
1371
					'rows' => array(
1372
						'hosting_company' => array( 'Hosting Company', fs_request_get( 'hosting_company' ) )
1373
					)
1374
				);
1375
			}
1376
1377
			// Add 'API Error' custom email section.
1378
			$custom_email_sections['api_error'] = array(
1379
				'title' => 'API Error',
1380
				'rows'  => array(
1381
					'ping' => array( is_string( $ping ) ? htmlentities( $ping ) : json_encode( $ping ) )
1382
				)
1383
			);
1384
1385
			// Send email with technical details to resolve CloudFlare's firewall unnecessary protection.
1386
			$this->send_email(
1387
				'[email protected]',                              // recipient
1388
				$title . ' [' . $this->get_plugin_name() . ']',  // subject
1389
				$custom_email_sections,
1390
				array( "Reply-To: $admin_email <$admin_email>" ) // headers
1391
			);
1392
1393
			$this->_admin_notices->add_sticky(
1394
				sprintf(
1395
					__fs( 'fix-request-sent-message', $this->_slug ),
1396
					'<a href="mailto:' . $admin_email . '">' . $admin_email . '</a>'
1397
				),
1398
				'server_details_sent'
1399
			);
1400
1401
			// Action was taken, tell that API connectivity troubleshooting should be off now.
1402
1403
			echo "1";
1404
			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...
1405
		}
1406
1407
		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...
1408
			$params = array();
1409
			fs_require_once_template( 'firewall-issues-js.php', $params );
1410
		}
1411
1412
		#endregion Connectivity Issues ------------------------------------------------------------------
1413
1414
		#region Email ------------------------------------------------------------------
1415
1416
		/**
1417
		 * Generates and sends an HTML email with customizable sections.
1418
		 *
1419
		 * @author Leo Fajardo (@leorw)
1420
		 * @since  1.1.2
1421
		 *
1422
		 * @param string $to_address
1423
		 * @param string $subject
1424
		 * @param array  $sections
1425
		 * @param array  $headers
1426
		 *
1427
		 * @return bool Whether the email contents were sent successfully.
1428
		 */
1429
		private function send_email(
1430
			$to_address,
1431
			$subject,
1432
			$sections = array(),
1433
			$headers = array()
1434
		) {
1435
			$default_sections = $this->get_email_sections();
1436
1437
			// Insert new sections or replace the default email sections.
1438
			if ( is_array( $sections ) && ! empty( $sections ) ) {
1439
				foreach ( $sections as $section_id => $custom_section ) {
1440
					if ( ! isset( $default_sections[ $section_id ] ) ) {
1441
						// If the section does not exist, add it.
1442
						$default_sections[ $section_id ] = $custom_section;
1443
					} else {
1444
						// If the section already exists, override it.
1445
						$current_section = $default_sections[ $section_id ];
1446
1447
						// Replace the current section's title if a custom section title exists.
1448
						if ( isset( $custom_section['title'] ) ) {
1449
							$current_section['title'] = $custom_section['title'];
1450
						}
1451
1452
						// Insert new rows under the current section or replace the default rows.
1453
						if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) {
1454
							foreach ( $custom_section['rows'] as $row_id => $row ) {
1455
								$current_section['rows'][ $row_id ] = $row;
1456
							}
1457
						}
1458
1459
						$default_sections[ $section_id ] = $current_section;
1460
					}
1461
				}
1462
			}
1463
1464
			$vars    = array( 'sections' => $default_sections );
1465
			$message = fs_get_template( 'email.php', $vars );
1466
1467
			// Set the type of email to HTML.
1468
			$headers[] = 'Content-type: text/html';
1469
1470
			$header_string = implode( "\r\n", $headers );
1471
1472
			return wp_mail(
1473
				$to_address,
1474
				$subject,
1475
				$message,
1476
				$header_string
1477
			);
1478
		}
1479
1480
		/**
1481
		 * Generates the data for the sections of the email content.
1482
		 *
1483
		 * @author Leo Fajardo (@leorw)
1484
		 * @since  1.1.2
1485
		 *
1486
		 * @return array
1487
		 */
1488
		private function get_email_sections() {
1489
			self::require_pluggable_essentials();
1490
1491
			// Retrieve the current user's information so that we can get the user's email, first name, and last name below.
1492
			$current_user = wp_get_current_user();
1493
1494
			// Retrieve the cURL version information so that we can get the version number below.
1495
			$curl_version_information = curl_version();
1496
1497
			$active_plugin = $this->get_active_plugins();
1498
1499
			// Generate the list of active plugins separated by new line. 
1500
			$active_plugin_string = '';
1501
			foreach ( $active_plugin as $plugin ) {
1502
				$active_plugin_string .= sprintf(
1503
					'<a href="%s">%s</a> [v%s]<br>',
1504
					$plugin['PluginURI'],
1505
					$plugin['Name'],
1506
					$plugin['Version']
1507
				);
1508
			}
1509
1510
			$server_ip = WP_FS__REMOTE_ADDR;
1511
1512
			// Generate the default email sections.
1513
			$sections = array(
1514
				'sdk'     => array(
1515
					'title' => 'SDK',
1516
					'rows'  => array(
1517
						'fs_version'   => array( 'FS Version', $this->version ),
1518
						'curl_version' => array( 'cURL Version', $curl_version_information['version'] )
1519
					)
1520
				),
1521
				'plugin'  => array(
1522
					'title' => 'Plugin',
1523
					'rows'  => array(
1524
						'name'    => array( 'Name', $this->get_plugin_name() ),
1525
						'version' => array( 'Version', $this->get_plugin_version() )
1526
					)
1527
				),
1528
				'site'    => array(
1529
					'title' => 'Site',
1530
					'rows'  => array(
1531
						'address'     => array( 'Address', site_url() ),
1532
						'host'        => array(
1533
							'HTTP_HOST',
1534
							( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' )
1535
						),
1536
						'server_addr' => array(
1537
							'SERVER_ADDR',
1538
							'<a href="http://www.projecthoneypot.org/ip_' . $server_ip . '">' . $server_ip . '</a>'
1539
						)
1540
					)
1541
				),
1542
				'user'    => array(
1543
					'title' => 'User',
1544
					'rows'  => array(
1545
						'email' => array( 'Email', $current_user->user_email ),
1546
						'first' => array( 'First', $current_user->user_firstname ),
1547
						'last'  => array( 'Last', $current_user->user_lastname )
1548
					)
1549
				),
1550
				'plugins' => array(
1551
					'title' => 'Plugins',
1552
					'rows'  => array(
1553
						'active_plugins' => array( 'Active Plugins', $active_plugin_string )
1554
					)
1555
				),
1556
			);
1557
1558
			// Allow the sections to be modified by other code.
1559
			$sections = $this->apply_filters( 'email_template_sections', $sections );
1560
1561
			return $sections;
1562
		}
1563
1564
		#endregion Email ------------------------------------------------------------------
1565
1566
		#region Initialization ------------------------------------------------------------------
1567
1568
		/**
1569
		 * Init plugin's Freemius instance.
1570
		 *
1571
		 * @author Vova Feldman (@svovaf)
1572
		 * @since  1.0.1
1573
		 *
1574
		 * @param number $id
1575
		 * @param string $public_key
1576
		 * @param bool   $is_live
1577
		 * @param bool   $is_premium
1578
		 */
1579
		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...
1580
			$this->_logger->entrance();
1581
1582
			$this->dynamic_init( array(
1583
				'id'         => $id,
1584
				'public_key' => $public_key,
1585
				'is_live'    => $is_live,
1586
				'is_premium' => $is_premium,
1587
			) );
1588
		}
1589
1590
		/**
1591
		 * Dynamic initiator, originally created to support initiation
1592
		 * with parent_id for add-ons.
1593
		 *
1594
		 * @author Vova Feldman (@svovaf)
1595
		 * @since  1.0.6
1596
		 *
1597
		 * @param array $plugin_info
1598
		 *
1599
		 * @throws Freemius_Exception
1600
		 */
1601
		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...
1602
			$this->_logger->entrance();
1603
1604
			$this->parse_settings( $plugin_info );
1605
1606
			if ( $this->should_stop_execution() ) {
1607
				return;
1608
			}
1609
1610
			if ( ! $this->is_registered() ) {
1611
				if ( $this->is_anonymous() ) {
1612
					// If user skipped, no need to test connectivity.
1613
					$this->_has_api_connection = true;
1614
					$this->_is_on              = true;
1615
				} else {
1616
					if ( ! $this->has_api_connectivity() ) {
1617
						if ( $this->_admin_notices->has_sticky( 'failed_connect_api' ) ) {
1618
							if ( ! $this->_enable_anonymous ) {
1619
								// If anonymous mode is disabled, add firewall admin-notice message.
1620
								add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) );
1621
1622
								add_action( "wp_ajax_{$this->_slug}_resolve_firewall_issues", array(
1623
									&$this,
1624
									'_email_about_firewall_issue'
1625
								) );
1626
							}
1627
						}
1628
1629
						return;
1630
					} else {
1631
						$this->_admin_notices->remove_sticky( 'failed_connect_api' );
1632
					}
1633
				}
1634
1635
				// Check if Freemius is on for the current plugin.
1636
				// This MUST be executed after all the plugin variables has been loaded.
1637
				if ( ! $this->is_on() ) {
1638
					return;
1639
				}
1640
			}
1641
1642
			if ( $this->has_api_connectivity() && $this->is_user_in_admin() ) {
1643
				/**
1644
				 * Schedule daily data sync cron if:
1645
				 *
1646
				 *  1. User opted-in (for tracking).
1647
				 *  2. If plugin has add-ons (update add-ons data).
1648
				 *  3. If skipped, but later upgraded (opted-in via upgrade).
1649
				 *
1650
				 * @author Vova Feldman (@svovaf)
1651
				 * @since  1.1.7.3
1652
				 *
1653
				 */
1654
				if ( $this->is_registered() ||
1655
				     ( ! $this->is_activation_mode() && $this->_has_addons )
1656
				) {
1657
1658
					$this->hook_callback_to_sync_cron();
1659
1660
					if ( ! $this->is_sync_cron_on() ) {
1661
						$this->schedule_sync_cron();
1662
					}
1663
				}
1664
1665
				/**
1666
				 * Check if requested for manual blocking background sync.
1667
				 */
1668
				if ( fs_request_has( 'background_sync' ) ) {
1669
					$this->run_manual_sync();
1670
				}
1671
			}
1672
1673
			if ($this->is_registered()){
1674
				$this->hook_callback_to_install_sync();
1675
			}
1676
1677
			if ( $this->is_addon() ) {
1678
				if ( $this->is_parent_plugin_installed() ) {
1679
					// Link to parent FS.
1680
					$this->_parent = self::get_instance_by_id( $this->_plugin->parent_plugin_id );
1681
1682
					// Get parent plugin reference.
1683
					$this->_parent_plugin = $this->_parent->get_plugin();
1684
				}
1685
			}
1686
1687
			if ( $this->is_user_in_admin() ) {
1688
				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...
1689
				if ( 'plugins.php' === $pagenow ) {
1690
					$this->hook_plugin_action_links();
1691
				}
1692
1693
				if ( $this->is_addon() ) {
1694
					if ( ! $this->is_parent_plugin_installed() ) {
1695
						$parent_name = $this->get_option( $plugin_info, 'parent_name', null );
1696
1697
						if ( isset( $plugin_info['parent'] ) ) {
1698
							$parent_name = $this->get_option( $plugin_info['parent'], 'name', null );
1699
						}
1700
1701
						$this->_admin_notices->add(
1702
							( ! empty( $parent_name ) ?
1703
								sprintf( __fs( 'addon-x-cannot-run-without-y', $this->_slug ), $this->get_plugin_name(), $parent_name ) :
1704
								sprintf( __fs( 'addon-x-cannot-run-without-parent', $this->_slug ), $this->get_plugin_name() )
1705
							),
1706
							__fs( 'oops', $this->_slug ) . '...',
1707
							'error'
1708
						);
1709
1710
						return;
1711
					} else {
1712
						if ( $this->_parent->is_registered() && ! $this->is_registered() ) {
1713
							// If parent plugin activated, automatically install add-on for the user.
1714
							$this->_activate_addon_account( $this->_parent );
1715
						}
1716
1717
						// @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic.
1718
						if ( $this->is_premium() ) {
1719
							// Remove add-on download admin-notice.
1720
							$this->_parent->_admin_notices->remove_sticky( 'addon_plan_upgraded_' . $this->_slug );
1721
						}
1722
1723
						$this->deactivate_premium_only_addon_without_license();
1724
					}
1725
				} else {
1726
					add_action( 'admin_init', array( &$this, '_admin_init_action' ) );
1727
1728
					if ( $this->has_addons() &&
1729
					     'plugin-information' === fs_request_get( 'tab', false ) &&
1730
					     $this->get_id() == fs_request_get( 'parent_plugin_id', false )
1731
					) {
1732
						require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php';
1733
1734
						new FS_Plugin_Info_Dialog( $this );
1735
					}
1736
				}
1737
1738
				if ( $this->is_premium() ) {
1739
					new FS_Plugin_Updater( $this );
1740
				}
1741
1742
//				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...
1743
//				     $this->is_anonymous() ||
1744
//				     $this->is_pending_activation()
1745
//				) {
1746
//					$this->_init_admin();
1747
//				}
1748
			}
1749
1750
			$this->do_action( 'initiated' );
1751
1752
			if ( ! $this->is_addon() ) {
1753
				if ( $this->is_registered() ) {
1754
					// Fix for upgrade from versions < 1.0.9.
1755
					if ( ! isset( $this->_storage->activation_timestamp ) ) {
1756
						$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...
1757
					}
1758
					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...
1759
						if ( isset( $this->_storage->prev_is_premium ) ) {
1760
							add_action( is_admin() ? 'admin_init' : 'init', array(
1761
								&$this,
1762
								'_plugin_code_type_changed'
1763
							) );
1764
						} else {
1765
							// Set for code type for the first time.
1766
							$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...
1767
						}
1768
					}
1769
1770
					$this->do_action( 'after_init_plugin_registered' );
1771
				} else if ( $this->is_anonymous() ) {
1772
					$this->do_action( 'after_init_plugin_anonymous' );
1773
				} else if ( $this->is_pending_activation() ) {
1774
					$this->do_action( 'after_init_plugin_pending_activations' );
1775
				}
1776
			} else {
1777
				if ( $this->is_registered() ) {
1778
					$this->do_action( 'after_init_addon_registered' );
1779
				} else if ( $this->is_anonymous() ) {
1780
					$this->do_action( 'after_init_addon_anonymous' );
1781
				} else if ( $this->is_pending_activation() ) {
1782
					$this->do_action( 'after_init_addon_pending_activations' );
1783
				}
1784
			}
1785
		}
1786
1787
		/**
1788
		 * Parse plugin's settings (as defined by the plugin dev).
1789
		 *
1790
		 * @author Vova Feldman (@svovaf)
1791
		 * @since  1.1.7.3
1792
		 *
1793
		 * @param array $plugin_info
1794
		 *
1795
		 * @throws \Freemius_Exception
1796
		 */
1797
		private function parse_settings( &$plugin_info ) {
1798
			$this->_logger->entrance();
1799
1800
			$id          = $this->get_numeric_option( $plugin_info, 'id', false );
1801
			$public_key  = $this->get_option( $plugin_info, 'public_key', false );
1802
			$secret_key  = $this->get_option( $plugin_info, 'secret_key', null );
1803
			$parent_id   = $this->get_numeric_option( $plugin_info, 'parent_id', null );
1804
			$parent_name = $this->get_option( $plugin_info, 'parent_name', null );
1805
1806
			if ( isset( $plugin_info['parent'] ) ) {
1807
				$parent_id = $this->get_numeric_option( $plugin_info['parent'], 'id', null );
1808
//				$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...
1809
//				$parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null );
1810
				$parent_name = $this->get_option( $plugin_info['parent'], 'name', null );
1811
			}
1812
1813
			if ( false === $id ) {
1814
				throw new Freemius_Exception( 'Plugin id parameter is not set.' );
1815
			}
1816
			if ( false === $public_key ) {
1817
				throw new Freemius_Exception( 'Plugin public_key parameter is not set.' );
1818
			}
1819
1820
			$plugin = ( $this->_plugin instanceof FS_Plugin ) ?
1821
				$this->_plugin :
1822
				new FS_Plugin();
1823
1824
			$plugin->update( array(
1825
				'id'               => $id,
1826
				'public_key'       => $public_key,
1827
				'slug'             => $this->_slug,
1828
				'parent_plugin_id' => $parent_id,
1829
				'version'          => $this->get_plugin_version(),
1830
				'title'            => $this->get_plugin_name(),
1831
				'file'             => $this->_plugin_basename,
1832
				'is_premium'       => $this->get_bool_option( $plugin_info, 'is_premium', true ),
1833
				'is_live'          => $this->get_bool_option( $plugin_info, 'is_live', true ),
1834
//				'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...
1835
			) );
1836
1837
			if ( $plugin->is_updated() ) {
1838
				// Update plugin details.
1839
				$this->_plugin = FS_Plugin_Manager::instance( $this->_slug )->store( $plugin );
1840
			}
1841
			// Set the secret key after storing the plugin, we don't want to store the key in the storage.
1842
			$this->_plugin->secret_key = $secret_key;
1843
1844
			if ( ! isset( $plugin_info['menu'] ) ) {
1845
				// Back compatibility to 1.1.2
1846
				$plugin_info['menu'] = array(
1847
					'slug' => isset( $plugin_info['menu_slug'] ) ?
1848
						$plugin_info['menu_slug'] :
1849
						$this->_slug
1850
				);
1851
			}
1852
1853
			$this->_menu = FS_Admin_Menu_Manager::instance( $this->_slug );
1854
			$this->_menu->init( $plugin_info['menu'], $this->is_addon() );
1855
1856
			$this->_has_addons       = $this->get_bool_option( $plugin_info, 'has_addons', false );
1857
			$this->_has_paid_plans   = $this->get_bool_option( $plugin_info, 'has_paid_plans', true );
1858
			$this->_is_org_compliant = $this->get_bool_option( $plugin_info, 'is_org_compliant', true );
1859
			$this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true );
1860
			$this->_permissions      = $this->get_option( $plugin_info, 'permissions', array() );
1861
		}
1862
1863
		/**
1864
		 * @param string[] $options
1865
		 * @param string   $key
1866
		 * @param mixed    $default
1867
		 *
1868
		 * @return bool
1869
		 */
1870
		private function get_option( &$options, $key, $default = false ) {
1871
			return ! empty( $options[ $key ] ) ? $options[ $key ] : $default;
1872
		}
1873
1874
		private function get_bool_option( &$options, $key, $default = false ) {
1875
			return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default;
1876
		}
1877
1878
		private function get_numeric_option( &$options, $key, $default = false ) {
1879
			return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default;
1880
		}
1881
1882
		/**
1883
		 * Gate keeper.
1884
		 *
1885
		 * @author Vova Feldman (@svovaf)
1886
		 * @since  1.1.7.3
1887
		 *
1888
		 * @return bool
1889
		 */
1890
		private function should_stop_execution() {
1891
			if ( $this->is_activation_mode() ) {
1892
				if ( ! is_admin() ) {
1893
					/**
1894
					 * If in activation mode, don't execute Freemius outside of the
1895
					 * admin dashboard.
1896
					 *
1897
					 * @author Vova Feldman (@svovaf)
1898
					 * @since  1.1.7.3
1899
					 */
1900
					return true;
1901
				}
1902
1903
				if ( ! WP_FS__IS_HTTP_REQUEST ) {
1904
					/**
1905
					 * If in activation and executed without HTTP context (e.g. CLI, Cronjob),
1906
					 * then don't start Freemius.
1907
					 *
1908
					 * @author Vova Feldman (@svovaf)
1909
					 * @since  1.1.6.3
1910
					 *
1911
					 * @link   https://wordpress.org/support/topic/errors-in-the-freemius-class-when-running-in-wordpress-in-cli
1912
					 */
1913
					return true;
1914
				}
1915
1916
				if ( $this->is_cron() ) {
1917
					/**
1918
					 * If in activation mode, don't execute Freemius during wp crons
1919
					 * (wp crons have HTTP context - called as HTTP request).
1920
					 *
1921
					 * @author Vova Feldman (@svovaf)
1922
					 * @since  1.1.7.3
1923
					 */
1924
					return true;
1925
				}
1926
1927
				if ( $this->is_ajax() && ! $this->_admin_notices->has_sticky( 'failed_connect_api' ) ) {
1928
					/**
1929
					 * During activation, if running in AJAX mode, unless there's a sticky
1930
					 * connectivity issue notice, don't run Freemius.
1931
					 *
1932
					 * @author Vova Feldman (@svovaf)
1933
					 * @since  1.1.7.3
1934
					 */
1935
					return true;
1936
				}
1937
			}
1938
1939
			return false;
1940
		}
1941
1942
		/**
1943
		 * Handles plugin's code type change (free <--> premium).
1944
		 *
1945
		 * @author Vova Feldman (@svovaf)
1946
		 * @since  1.0.9
1947
		 */
1948
		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...
1949
			// Schedule code type changes event.
1950
//			$this->sync_install();
1951
			$this->schedule_install_sync();
1952
1953
			if ( $this->is_premium() ) {
1954
				// Activated premium code.
1955
				$this->do_action( 'after_premium_version_activation' );
1956
1957
				// Remove all sticky messages related to download of the premium version.
1958
				$this->_admin_notices->remove_sticky( array(
1959
					'trial_started',
1960
					'plan_upgraded',
1961
					'plan_changed',
1962
				) );
1963
1964
				$this->_admin_notices->add_sticky(
1965
					__fs( 'premium-activated-message', $this->_slug ),
1966
					'premium_activated',
1967
					__fs( 'woot', $this->_slug ) . '!'
1968
				);
1969
			} else {
1970
				// Activated free code (after had the premium before).
1971
				$this->do_action( 'after_free_version_reactivation' );
1972
1973
				if ( $this->is_paying() && ! $this->is_premium() ) {
1974
					$this->_admin_notices->add_sticky(
1975
						sprintf(
1976
							__fs( 'you-have-x-license', $this->_slug ),
1977
							$this->_site->plan->title
1978
						) . ' ' . $this->_get_latest_download_link( sprintf(
1979
							__fs( 'download-x-version-now', $this->_slug ),
1980
							$this->_site->plan->title
1981
						) ),
1982
						'plan_upgraded',
1983
						__fs( 'yee-haw', $this->_slug ) . '!'
1984
					);
1985
				}
1986
			}
1987
1988
			// Update is_premium of latest version.
1989
			$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...
1990
		}
1991
1992
		#endregion Initialization ------------------------------------------------------------------
1993
1994
		#region Add-ons -------------------------------------------------------------------------
1995
1996
		/**
1997
		 * Check if add-on installed and activated on site.
1998
		 *
1999
		 * @author Vova Feldman (@svovaf)
2000
		 * @since  1.0.6
2001
		 *
2002
		 * @param string|number $slug_or_id
2003
		 *
2004
		 * @return bool
2005
		 */
2006
		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...
2007
			return self::has_instance( $slug_or_id );
2008
		}
2009
2010
		/**
2011
		 * Check if add-on was connected to install
2012
		 *
2013
		 * @author Vova Feldman (@svovaf)
2014
		 * @since  1.1.7
2015
		 *
2016
		 * @param string $slug
2017
		 *
2018
		 * @return bool
2019
		 */
2020
		function is_addon_connected( $slug ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2021
			$sites = self::get_all_sites();
2022
2023
			if ( ! isset( $sites[ $slug ] ) ) {
2024
				return false;
2025
			}
2026
2027
			$site = $sites[ $slug ];
2028
2029
			$plugin = FS_Plugin_Manager::instance( $slug )->get();
2030
2031
			if ( $plugin->parent_plugin_id != $this->_plugin->id ) {
2032
				// The given slug do NOT belong to any of the plugin's add-ons.
2033
				return false;
2034
			}
2035
2036
			return ( is_object( $site ) &&
2037
			         is_numeric( $site->id ) &&
2038
			         is_numeric( $site->user_id ) &&
2039
			         is_object( $site->plan )
2040
			);
2041
		}
2042
2043
		/**
2044
		 * Determines if add-on installed.
2045
		 *
2046
		 * NOTE: This is a heuristic and only works if the folder/file named as the slug.
2047
		 *
2048
		 * @author Vova Feldman (@svovaf)
2049
		 * @since  1.0.6
2050
		 *
2051
		 * @param string $slug
2052
		 *
2053
		 * @return bool
2054
		 */
2055
		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...
2056
			return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $slug ) ) );
2057
		}
2058
2059
		/**
2060
		 * Get add-on basename.
2061
		 *
2062
		 * @author Vova Feldman (@svovaf)
2063
		 * @since  1.0.6
2064
		 *
2065
		 * @param string $slug
2066
		 *
2067
		 * @return string
2068
		 */
2069
		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...
2070
			if ( $this->is_addon_activated( $slug ) ) {
2071
				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...
2072
			}
2073
2074
			$premium_basename = $slug . '-premium/' . $slug . '.php';
2075
2076
			if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_basename ) ) ) {
2077
				return $premium_basename;
2078
			}
2079
2080
			$free_basename = $slug . '/' . $slug . '.php';
2081
2082
			return $free_basename;
2083
		}
2084
2085
		/**
2086
		 * Get installed add-ons instances.
2087
		 *
2088
		 * @author Vova Feldman (@svovaf)
2089
		 * @since  1.0.6
2090
		 *
2091
		 * @return Freemius[]
2092
		 */
2093
		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...
2094
			$installed_addons = array();
2095
			foreach ( self::$_instances as $slug => $instance ) {
2096
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
2097
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
2098
						$installed_addons[] = $instance;
2099
					}
2100
				}
2101
			}
2102
2103
			return $installed_addons;
2104
		}
2105
2106
		/**
2107
		 * Check if any add-ons of the plugin are installed.
2108
		 *
2109
		 * @author Leo Fajardo (@leorw)
2110
		 * @since  1.1.1
2111
		 *
2112
		 * @return bool
2113
		 */
2114
		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...
2115
			if ( ! $this->has_addons() ) {
2116
				return false;
2117
			}
2118
2119
			foreach ( self::$_instances as $slug => $instance ) {
2120
				if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) {
2121
					if ( $this->_plugin->id == $instance->_parent_plugin->id ) {
2122
						return true;
2123
					}
2124
				}
2125
			}
2126
2127
			return false;
2128
		}
2129
2130
		/**
2131
		 * Tell Freemius that the current plugin is an add-on.
2132
		 *
2133
		 * @author Vova Feldman (@svovaf)
2134
		 * @since  1.0.6
2135
		 *
2136
		 * @param number $parent_plugin_id The parent plugin ID
2137
		 */
2138
		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...
2139
			$this->_plugin->parent_plugin_id = $parent_plugin_id;
2140
		}
2141
2142
		/**
2143
		 * @author Vova Feldman (@svovaf)
2144
		 * @since  1.0.6
2145
		 *
2146
		 * @return bool
2147
		 */
2148
		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...
2149
			return isset( $this->_plugin->parent_plugin_id ) && is_numeric( $this->_plugin->parent_plugin_id );
2150
		}
2151
2152
		/**
2153
		 * Deactivate add-on if it's premium only and the user does't have a valid license.
2154
		 *
2155
		 * @param bool $is_after_trial_cancel
2156
		 *
2157
		 * @return bool If add-on was deactivated.
2158
		 */
2159
		private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) {
2160
			if ( ! $this->has_free_plan() &&
2161
			     ! $this->has_features_enabled_license() &&
2162
			     ! $this->_has_premium_license()
2163
			) {
2164
				deactivate_plugins( array( $this->_plugin_basename ), true );
2165
2166
				$this->_parent->_admin_notices->add_sticky(
2167
					sprintf(
2168
						__fs( ( $is_after_trial_cancel ?
2169
								'addon-trial-cancelled-message' :
2170
								'addon-no-license-message' ),
2171
							$this->_parent->_slug
2172
						),
2173
						'<b>' . $this->_plugin->title . '</b>'
2174
					) . ' ' . sprintf(
2175
						'<a href="%s" aria-label="%s" class="button button-primary" style="margin-left: 10px; vertical-align: middle;">%s &nbsp;&#10140;</a>',
2176
						$this->_parent->addon_url( $this->_slug ),
2177
						esc_attr( sprintf( __fs( 'more-information-about-x', $this->_parent->_slug ), $this->_plugin->title ) ),
2178
						__fs( 'purchase-license', $this->_parent->_slug )
2179
					),
2180
					'no_addon_license',
2181
					( $is_after_trial_cancel ? '' : __fs( 'oops', $this->_parent->_slug ) . '...' ),
2182
					( $is_after_trial_cancel ? 'success' : 'error' )
2183
				);
2184
2185
				return true;
2186
			}
2187
2188
			return false;
2189
		}
2190
2191
		#endregion ------------------------------------------------------------------
2192
2193
		#region Sandbox ------------------------------------------------------------------
2194
2195
		/**
2196
		 * Set Freemius into sandbox mode for debugging.
2197
		 *
2198
		 * @author Vova Feldman (@svovaf)
2199
		 * @since  1.0.4
2200
		 *
2201
		 * @param string $secret_key
2202
		 */
2203
		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...
2204
			$this->_plugin->secret_key = $secret_key;
2205
2206
			// Update plugin details.
2207
			FS_Plugin_Manager::instance( $this->_slug )->update( $this->_plugin, true );
2208
		}
2209
2210
		/**
2211
		 * Check if running payments in sandbox mode.
2212
		 *
2213
		 * @author Vova Feldman (@svovaf)
2214
		 * @since  1.0.4
2215
		 *
2216
		 * @return bool
2217
		 */
2218
		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...
2219
			return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key );
2220
		}
2221
2222
		#endregion Sandbox ------------------------------------------------------------------
2223
2224
		/**
2225
		 * Check if running test vs. live plugin.
2226
		 *
2227
		 * @author Vova Feldman (@svovaf)
2228
		 * @since  1.0.5
2229
		 *
2230
		 * @return bool
2231
		 */
2232
		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...
2233
			return $this->_plugin->is_live;
2234
		}
2235
2236
		/**
2237
		 * Check if the user skipped connecting the account with Freemius.
2238
		 *
2239
		 * @author Vova Feldman (@svovaf)
2240
		 * @since  1.0.7
2241
		 *
2242
		 * @return bool
2243
		 */
2244
		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...
2245
			if ( ! isset( $this->_is_anonymous ) ) {
2246
				if ( ! isset( $this->_storage->is_anonymous ) ) {
2247
					// Not skipped.
2248
					$this->_is_anonymous = false;
2249
				} 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...
2250
					// For back compatibility, since the variable was boolean before.
2251
					$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...
2252
2253
					// Upgrade stored data format to 1.1.3 format.
2254
					$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...
2255
				} else {
2256
					// Version 1.1.3 and later.
2257
					$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...
2258
				}
2259
			}
2260
2261
			return $this->_is_anonymous;
2262
		}
2263
2264
		/**
2265
		 * Check if user connected his account and install pending email activation.
2266
		 *
2267
		 * @author Vova Feldman (@svovaf)
2268
		 * @since  1.0.7
2269
		 *
2270
		 * @return bool
2271
		 */
2272
		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...
2273
			return $this->_storage->get( 'is_pending_activation', false );
2274
		}
2275
2276
		/**
2277
		 * Check if plugin must be WordPress.org compliant.
2278
		 *
2279
		 * @since 1.0.7
2280
		 *
2281
		 * @return bool
2282
		 */
2283
		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...
2284
			return $this->_is_org_compliant;
2285
		}
2286
2287
		#region Daily Sync Cron ------------------------------------------------------------------
2288
2289
		/**
2290
		 * @author Vova Feldman (@svovaf)
2291
		 * @since  1.1.7.3
2292
		 */
2293
		private function run_manual_sync() {
2294
			$this->require_pluggable_essentials();
2295
2296
			if ( ! current_user_can( 'activate_plugins' ) ) {
2297
				return;
2298
			}
2299
2300
			// Run manual sync.
2301
			$this->_sync_cron();
2302
2303
			// Reschedule next cron to run 24 hours from now (performance optimization).
2304
			$this->clear_sync_cron();
2305
2306
			$this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false );
2307
		}
2308
2309
		/**
2310
		 * Data sync cron job. Replaces the background sync non blocking HTTP request
2311
		 * that doesn't halt page loading.
2312
		 *
2313
		 * @author Vova Feldman (@svovaf)
2314
		 * @since  1.1.7.3
2315
		 */
2316
		function _sync_cron() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2317
			$this->_logger->entrance();
2318
2319
			// Store the last time data sync was executed.
2320
			$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...
2321
2322
			// Check if API is temporary down.
2323
			if ( FS_Api::is_temporary_down() ) {
2324
				return;
2325
			}
2326
2327
			// @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours.
2328
2329
			if ( $this->is_registered() ) {
2330
				if ( $this->has_paid_plan() ) {
2331
					// Initiate background plan sync.
2332
					$this->_sync_license( true );
2333
2334
					if ( $this->is_paying() ) {
2335
						// Check for premium plugin updates.
2336
						$this->_check_updates( true );
2337
					}
2338
				} else {
2339
					// Sync install (only if something changed locally).
2340
					$this->sync_install();
2341
				}
2342
			}
2343
2344
			if ( ! $this->is_addon() && $this->_has_addons ) {
2345
				// Sync add-ons collection.
2346
				$this->_sync_addons( true );
2347
			}
2348
		}
2349
2350
		/**
2351
		 * Check if sync was executed in the last $period of seconds.
2352
		 *
2353
		 * @author Vova Feldman (@svovaf)
2354
		 * @since  1.1.7.3
2355
		 *
2356
		 * @param int $period In seconds
2357
		 *
2358
		 * @return bool
2359
		 */
2360
		private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) {
2361
			if ( ! isset( $this->_storage->sync_timestamp ) ) {
2362
				return false;
2363
			}
2364
2365
			return ( $this->_storage->sync_timestamp > ( WP_FS__SCRIPT_START_TIME - $period ) );
0 ignored issues
show
Documentation introduced by
The property sync_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...
2366
		}
2367
2368
		/**
2369
		 * @author Vova Feldman (@svovaf)
2370
		 * @since  1.1.7.3
2371
		 *
2372
		 * @return bool
2373
		 */
2374
		private function is_sync_cron_on() {
2375
			/**
2376
			 * @var object $sync_cron_data
2377
			 */
2378
			$sync_cron_data = $this->_storage->get( 'sync_cron', null );
2379
2380
			return ( ! is_null( $sync_cron_data ) && true === $sync_cron_data->on );
2381
		}
2382
2383
		/**
2384
		 * @author Vova Feldman (@svovaf)
2385
		 * @since  1.1.7.3
2386
		 *
2387
		 * @param int  $start_at        Defaults to now.
2388
		 * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise,
2389
		 *                              schedule job to start right away.
2390
		 */
2391
		private function schedule_sync_cron( $start_at = WP_FS__SCRIPT_START_TIME, $randomize_start = true ) {
2392
			$this->_logger->entrance();
2393
2394
			if ( $randomize_start ) {
2395
				// Schedule first sync with a random 12 hour time range from now.
2396
				$start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) );
2397
			}
2398
2399
			// Schedule daily WP cron.
2400
			wp_schedule_event(
2401
				$start_at,
2402
				'daily',
2403
				$this->get_action_tag( 'data_sync' )
2404
			);
2405
2406
			$this->_storage->store( 'sync_cron', (object) array(
2407
				'version'     => $this->get_plugin_version(),
2408
				'sdk_version' => $this->version,
2409
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
2410
				'on'          => true,
2411
			) );
2412
		}
2413
2414
		/**
2415
		 * Add the actual sync function to the cron job hook.
2416
		 *
2417
		 * @author Vova Feldman (@svovaf)
2418
		 * @since  1.1.7.3
2419
		 */
2420
		private function hook_callback_to_sync_cron() {
2421
			$this->add_action( 'data_sync', array( &$this, '_sync_cron' ) );
2422
		}
2423
2424
		/**
2425
		 * @author Vova Feldman (@svovaf)
2426
		 * @since  1.1.7.3
2427
		 */
2428
		private function clear_sync_cron() {
2429
			$this->_logger->entrance();
2430
2431
			if ( ! $this->is_sync_cron_on() ) {
2432
				return;
2433
			}
2434
2435
			$this->_storage->remove( 'sync_cron' );
2436
2437
			wp_clear_scheduled_hook( $this->get_action_tag( 'data_sync' ) );
2438
		}
2439
2440
		/**
2441
		 * Unix timestamp for next sync cron execution or false if not scheduled.
2442
		 *
2443
		 * @author Vova Feldman (@svovaf)
2444
		 * @since  1.1.7.3
2445
		 *
2446
		 * @return int|false
2447
		 */
2448
		function next_sync_cron() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2449
			$this->_logger->entrance();
2450
2451
			if ( ! $this->is_sync_cron_on() ) {
2452
				return false;
2453
			}
2454
2455
			return wp_next_scheduled( $this->get_action_tag( 'data_sync' ) );
2456
		}
2457
2458
		/**
2459
		 * Unix timestamp for previous sync cron execution or false if never executed.
2460
		 *
2461
		 * @author Vova Feldman (@svovaf)
2462
		 * @since  1.1.7.3
2463
		 *
2464
		 * @return int|false
2465
		 */
2466
		function last_sync_cron() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2467
			$this->_logger->entrance();
2468
2469
			return $this->_storage->get( 'sync_timestamp' );
2470
		}
2471
2472
		#endregion Daily Sync Cron ------------------------------------------------------------------
2473
2474
		#region Async Install Sync ------------------------------------------------------------------
2475
2476
		/**
2477
		 * @author Vova Feldman (@svovaf)
2478
		 * @since  1.1.7.3
2479
		 *
2480
		 * @return bool
2481
		 */
2482
		private function is_install_sync_scheduled() {
2483
			/**
2484
			 * @var object $cron_data
2485
			 */
2486
			$cron_data = $this->_storage->get( 'install_sync_cron', null );
2487
2488
			return ( ! is_null( $cron_data ) && true === $cron_data->on );
2489
		}
2490
2491
		/**
2492
		 * Instead of running blocking install sync event, execute non blocking scheduled wp-cron.
2493
		 *
2494
		 * @author Vova Feldman (@svovaf)
2495
		 * @since  1.1.7.3
2496
		 */
2497
		private function schedule_install_sync() {
2498
			$this->_logger->entrance();
2499
2500
			$this->clear_install_sync_cron();
2501
2502
			// Schedule immediate install sync.
2503
			wp_schedule_single_event(
2504
				WP_FS__SCRIPT_START_TIME,
2505
				$this->get_action_tag( 'install_sync' )
2506
			);
2507
2508
			$this->_storage->store( 'install_sync_cron', (object) array(
2509
				'version'     => $this->get_plugin_version(),
2510
				'sdk_version' => $this->version,
2511
				'timestamp'   => WP_FS__SCRIPT_START_TIME,
2512
				'on'          => true,
2513
			) );
2514
		}
2515
2516
		/**
2517
		 * Unix timestamp for previous install sync cron execution or false if never executed.
2518
		 *
2519
		 * @todo There's some very strange bug that $this->_storage->install_sync_timestamp value is not being updated. But for sure the sync event is working.
2520
		 *
2521
		 * @author Vova Feldman (@svovaf)
2522
		 * @since  1.1.7.3
2523
		 *
2524
		 * @return int|false
2525
		 */
2526
		function last_install_sync() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2527
			$this->_logger->entrance();
2528
2529
			return $this->_storage->get( 'install_sync_timestamp' );
2530
		}
2531
2532
		/**
2533
		 * Unix timestamp for next install sync cron execution or false if not scheduled.
2534
		 *
2535
		 * @author Vova Feldman (@svovaf)
2536
		 * @since  1.1.7.3
2537
		 *
2538
		 * @return int|false
2539
		 */
2540
		function next_install_sync() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2541
			$this->_logger->entrance();
2542
2543
			if ( ! $this->is_install_sync_scheduled() ) {
2544
				return false;
2545
			}
2546
2547
			return wp_next_scheduled( $this->get_action_tag( 'install_sync' ) );
2548
		}
2549
2550
		/**
2551
		 * Add the actual install sync function to the cron job hook.
2552
		 *
2553
		 * @author Vova Feldman (@svovaf)
2554
		 * @since  1.1.7.3
2555
		 */
2556
		private function hook_callback_to_install_sync() {
2557
			$this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) );
2558
		}
2559
2560
		/**
2561
		 * @author Vova Feldman (@svovaf)
2562
		 * @since  1.1.7.3
2563
		 */
2564
		private function clear_install_sync_cron() {
2565
			$this->_logger->entrance();
2566
2567
			if ( ! $this->is_install_sync_scheduled() ) {
2568
				return;
2569
			}
2570
2571
			$this->_storage->remove( 'install_sync_cron' );
2572
2573
			wp_clear_scheduled_hook( $this->get_action_tag( 'install_sync' ) );
2574
		}
2575
2576
		/**
2577
		 * @author Vova Feldman (@svovaf)
2578
		 * @since  1.1.7.3
2579
		 */
2580
		public function _run_sync_install() {
2581
			$this->_logger->entrance();
2582
2583
			// Update last install sync timestamp.
2584
			$this->_storage->install_sync_timestamp = time();
0 ignored issues
show
Documentation introduced by
The property install_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...
2585
2586
			$this->sync_install( array(), true );
2587
		}
2588
2589
		#endregion Async Install Sync ------------------------------------------------------------------
2590
2591
		/**
2592
		 * Show a notice that activation is currently pending.
2593
		 *
2594
		 * @author Vova Feldman (@svovaf)
2595
		 * @since  1.0.7
2596
		 *
2597
		 * @param bool|string $email
2598
		 */
2599
		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...
2600
			if ( ! is_string( $email ) ) {
2601
				$current_user = wp_get_current_user();
2602
				$email        = $current_user->user_email;
2603
			}
2604
2605
			$this->_admin_notices->add_sticky(
2606
				sprintf(
2607
					__fs( 'pending-activation-message', $this->_slug ),
2608
					'<b>' . $this->get_plugin_name() . '</b>',
2609
					'<b>' . $email . '</b>'
2610
				),
2611
				'activation_pending',
2612
				'Thanks!'
2613
			);
2614
		}
2615
2616
		/**
2617
		 * Check if currently in plugin activation.
2618
		 *
2619
		 * @author Vova Feldman (@svovaf)
2620
		 * @since  1.1.4
2621
		 *
2622
		 * @return bool
2623
		 */
2624
		function is_plugin_activation() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2625
			return get_option( "fs_{$this->_slug}_activated", false );
2626
		}
2627
2628
		/**
2629
		 *
2630
		 * NOTE: admin_menu action executed before admin_init.
2631
		 *
2632
		 * @author Vova Feldman (@svovaf)
2633
		 * @since  1.0.7
2634
		 */
2635
		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...
2636
			/**
2637
			 * Automatically redirect to connect/activation page after plugin activation.
2638
			 *
2639
			 * @since 1.1.7 Do NOT redirect to opt-in when running in network admin mode.
2640
			 */
2641
			if ( $this->is_plugin_activation() ) {
2642
				delete_option( "fs_{$this->_slug}_activated" );
2643
2644
				if ( ! function_exists( 'is_network_admin' ) || ! is_network_admin() ) {
2645
					$this->_redirect_on_activation_hook();
2646
2647
					return;
2648
				}
2649
			}
2650
2651
			if ( fs_request_is_action( $this->_slug . '_skip_activation' ) ) {
2652
				check_admin_referer( $this->_slug . '_skip_activation' );
2653
2654
				$this->skip_connection();
2655
2656
				if ( fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) ) ) {
2657
					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...
2658
				}
2659
			}
2660
2661
			if ( ! $this->is_addon() && ! $this->is_registered() && ! $this->is_anonymous() ) {
2662
				if ( ! $this->is_pending_activation() ) {
2663
					if ( ! $this->_menu->is_activation_page() ) {
2664
						if ( $this->is_plugin_new_install() ) {
2665
							// Show notice for new plugin installations.
2666
							$this->_admin_notices->add(
2667
								sprintf(
2668
									__fs( 'you-are-step-away', $this->_slug ),
2669
									sprintf( '<b><a href="%s">%s</a></b>',
2670
										$this->get_activation_url(),
2671
										sprintf( __fs( 'activate-x-now', $this->_slug ), $this->get_plugin_name() )
2672
									)
2673
								),
2674
								'',
2675
								'update-nag'
2676
							);
2677
						} else {
2678
							if ( ! isset( $this->_storage->sticky_optin_added ) ) {
2679
								$this->_storage->sticky_optin_added = true;
0 ignored issues
show
Documentation introduced by
The property sticky_optin_added 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...
2680
2681
								// Show notice for new plugin installations.
2682
								$this->_admin_notices->add_sticky(
2683
									sprintf(
2684
										__fs( 'few-plugin-tweaks', $this->_slug ),
2685
										sprintf( '<b><a href="%s">%s</a></b>',
2686
											$this->get_activation_url(),
2687
											sprintf( __fs( 'optin-x-now', $this->_slug ), $this->get_plugin_name() )
2688
										)
2689
									),
2690
									'connect_account',
2691
									'',
2692
									'update-nag'
2693
								);
2694
							}
2695
2696
							if ( $this->has_filter( 'optin_pointer_element' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->has_filter('optin_pointer_element') of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2697
								// Don't show admin nag if plugin update.
2698
								wp_enqueue_script( 'wp-pointer' );
2699
								wp_enqueue_style( 'wp-pointer' );
2700
2701
								$this->_enqueue_connect_essentials();
2702
2703
								add_action( 'admin_print_footer_scripts', array(
2704
									$this,
2705
									'_add_connect_pointer_script'
2706
								) );
2707
							}
2708
2709
						}
2710
					}
2711
				}
2712
			}
2713
2714
			$this->_add_upgrade_action_link();
2715
		}
2716
2717
		/**
2718
		 * Enqueue connect requires scripts and styles.
2719
		 *
2720
		 * @author Vova Feldman (@svovaf)
2721
		 * @since  1.1.4
2722
		 */
2723
		function _enqueue_connect_essentials() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2724
			wp_enqueue_script( 'jquery' );
2725
			wp_enqueue_script( 'json2' );
2726
2727
			fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' );
2728
			fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' );
2729
2730
			fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' );
2731
		}
2732
2733
		/**
2734
		 * Add connect / opt-in pointer.
2735
		 *
2736
		 * @author Vova Feldman (@svovaf)
2737
		 * @since  1.1.4
2738
		 */
2739
		function _add_connect_pointer_script() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2740
			$vars            = array( 'slug' => $this->_slug );
2741
			$pointer_content = fs_get_template( 'connect.php', $vars );
2742
			?>
2743
			<script type="text/javascript">// <![CDATA[
2744
				jQuery(document).ready(function ($) {
2745
					if ('undefined' !== typeof(jQuery().pointer)) {
2746
2747
						var element = <?php echo $this->apply_filters('optin_pointer_element', '$("#non_existing_element");') ?>;
2748
2749
						if (element.length > 0) {
2750
							var optin = $(element).pointer($.extend(true, {}, {
2751
								content     : <?php echo json_encode($pointer_content) ?>,
2752
								position    : {
2753
									edge : 'left',
2754
									align: 'center'
2755
								},
2756
								buttons     : function () {
2757
									// Don't show pointer buttons.
2758
									return '';
2759
								},
2760
								pointerWidth: 482
2761
							}, <?php echo $this->apply_filters('optin_pointer_options_json', '{}') ?>));
2762
2763
							<?php
2764
							echo $this->apply_filters('optin_pointer_execute', "
2765
2766
							optin.pointer('open');
2767
2768
							// Tag the opt-in pointer with custom class.
2769
							$('.wp-pointer #fs_connect')
2770
								.parents('.wp-pointer.wp-pointer-top')
2771
								.addClass('fs-opt-in-pointer');
2772
2773
							", 'element', 'optin') ?>
2774
						}
2775
					}
2776
				});
2777
				// ]]></script>
2778
		<?php
2779
		}
2780
2781
		/**
2782
		 * Return current page's URL.
2783
		 *
2784
		 * @author Vova Feldman (@svovaf)
2785
		 * @since  1.0.7
2786
		 *
2787
		 * @return string
2788
		 */
2789
		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...
2790
			$url = 'http';
2791
2792
			if ( isset( $_SERVER["HTTPS"] ) ) {
2793
				if ( $_SERVER["HTTPS"] == "on" ) {
2794
					$url .= "s";
2795
				}
2796
			}
2797
			$url .= "://";
2798
			if ( $_SERVER["SERVER_PORT"] != "80" ) {
2799
				$url .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
2800
			} else {
2801
				$url .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
2802
			}
2803
2804
			return esc_url( $url );
2805
		}
2806
2807
		/**
2808
		 * Check if the current page is the plugin's main admin settings page.
2809
		 *
2810
		 * @author Vova Feldman (@svovaf)
2811
		 * @since  1.0.7
2812
		 *
2813
		 * @return bool
2814
		 */
2815
		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...
2816
			return fs_is_plugin_page( $this->_menu->get_raw_slug() ) ||
2817
			       fs_is_plugin_page( $this->_slug );
2818
		}
2819
2820
		/* Events
2821
		------------------------------------------------------------------------------------------------------------------*/
2822
		/**
2823
		 * Delete site install from Database.
2824
		 *
2825
		 * @author Vova Feldman (@svovaf)
2826
		 * @since  1.0.1
2827
		 *
2828
		 * @param bool $store
2829
		 */
2830
		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...
2831
			$sites = self::get_all_sites();
2832
2833
			if ( isset( $sites[ $this->_slug ] ) ) {
2834
				unset( $sites[ $this->_slug ] );
2835
			}
2836
2837
			self::$_accounts->set_option( 'sites', $sites, $store );
2838
		}
2839
2840
		/**
2841
		 * Delete plugin's plans information.
2842
		 *
2843
		 * @param bool $store Flush to Database if true.
2844
		 *
2845
		 * @author Vova Feldman (@svovaf)
2846
		 * @since  1.0.9
2847
		 */
2848
		private function _delete_plans( $store = true ) {
2849
			$this->_logger->entrance();
2850
2851
			$plans = self::get_all_plans();
2852
2853
			unset( $plans[ $this->_slug ] );
2854
2855
			self::$_accounts->set_option( 'plans', $plans, $store );
2856
		}
2857
2858
		/**
2859
		 * Delete all plugin licenses.
2860
		 *
2861
		 * @author Vova Feldman (@svovaf)
2862
		 * @since  1.0.9
2863
		 *
2864
		 * @param bool        $store
2865
		 * @param string|bool $plugin_slug
2866
		 */
2867
		private function _delete_licenses( $store = true, $plugin_slug = false ) {
2868
			$this->_logger->entrance();
2869
2870
			$all_licenses = self::get_all_licenses();
2871
2872
			if ( ! is_string( $plugin_slug ) ) {
2873
				$plugin_slug = $this->_slug;
2874
			}
2875
2876
			unset( $all_licenses[ $plugin_slug ] );
2877
2878
			self::$_accounts->set_option( 'licenses', $all_licenses, $store );
2879
		}
2880
2881
		/**
2882
		 * Check if Freemius was added on new plugin installation.
2883
		 *
2884
		 * @author Vova Feldman (@svovaf)
2885
		 * @since  1.1.5
2886
		 *
2887
		 * @return bool
2888
		 */
2889
		function is_plugin_new_install() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
2890
			return isset( $this->_storage->is_plugin_new_install ) &&
2891
			       $this->_storage->is_plugin_new_install;
0 ignored issues
show
Documentation introduced by
The property is_plugin_new_install 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...
2892
		}
2893
2894
		/**
2895
		 * Plugin activated hook.
2896
		 *
2897
		 * @author Vova Feldman (@svovaf)
2898
		 * @since  1.0.1
2899
		 *
2900
		 * @uses   FS_Api
2901
		 */
2902
		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...
2903
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2904
2905
			if ( ! current_user_can( 'activate_plugins' ) ) {
2906
				return;
2907
			}
2908
2909
			// Clear API cache on activation.
2910
			FS_Api::clear_cache();
2911
2912
			if ( $this->is_registered() ) {
2913
				// Schedule re-activation event and sync.
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2914
//				$this->sync_install( array(), true );
2915
				$this->schedule_install_sync();
2916
2917
				/**
2918
				 * @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.
2919
				 */
2920
				if ( $this->_plugin_basename !== $this->_free_plugin_basename ) {
2921
					// Deactivate Free plugin version on premium plugin activation.
2922
					deactivate_plugins( $this->_free_plugin_basename );
2923
2924
					$this->_admin_notices->add(
2925
						sprintf( __fs( 'successful-version-upgrade-message', $this->_slug ), sprintf( '<b>%s</b>', $this->_plugin->title ) ),
2926
						__fs( 'woot', $this->_slug ) . '!'
2927
					);
2928
				}
2929
			} else if ( $this->is_anonymous() ) {
2930
				/**
2931
				 * Reset "skipped" click cache on the following:
2932
				 *  1. Development mode.
2933
				 *  2. If the user skipped the exact same version before.
2934
				 *
2935
				 * @todo 3. If explicitly asked to retry after every activation.
2936
				 */
2937
				if ( WP_FS__DEV_MODE ||
2938
				     $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...
2939
				) {
2940
					$this->reset_anonymous_mode();
2941
				}
2942
			}
2943
2944
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
2945
				/**
2946
				 * If no previous version of plugin's version exist, it means that it's either
2947
				 * the first time that the plugin installed on the site, or the plugin was installed
2948
				 * before but didn't have Freemius integrated.
2949
				 *
2950
				 * Since register_activation_hook() do NOT fires on updates since 3.1, and only fires
2951
				 * on manual activation via the dashboard, is_plugin_activation() is TRUE
2952
				 * only after immediate activation.
2953
				 *
2954
				 * @since 1.1.4
2955
				 * @link  https://make.wordpress.org/core/2010/10/27/plugin-activation-hooks-no-longer-fire-for-updates/
2956
				 */
2957
				$this->_storage->is_plugin_new_install = empty( $this->_storage->plugin_last_version );
0 ignored issues
show
Documentation introduced by
The property is_plugin_new_install 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_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...
2958
			}
2959
2960
			if ( $this->has_api_connectivity( WP_FS__DEV_MODE ) ) {
2961
				// Store hint that the plugin was just activated to enable auto-redirection to settings.
2962
				add_option( "fs_{$this->_slug}_activated", true );
2963
			}
2964
		}
2965
2966
		/**
2967
		 * Delete account.
2968
		 *
2969
		 * @author Vova Feldman (@svovaf)
2970
		 * @since  1.0.3
2971
		 *
2972
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
2973
		 */
2974
		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...
2975
			$this->_logger->entrance( 'slug = ' . $this->_slug );
2976
2977
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
2978
				return;
2979
			}
2980
2981
			$this->do_action( 'before_account_delete' );
2982
2983
			// Clear all admin notices.
2984
			$this->_admin_notices->clear_all_sticky();
2985
2986
			$this->_delete_site( false );
2987
2988
			$this->_delete_plans( false );
2989
2990
			$this->_delete_licenses( false );
2991
2992
			// Delete add-ons related to plugin's account.
2993
			$this->_delete_account_addons( false );
2994
2995
			// @todo Delete plans and licenses of add-ons.
2996
2997
			self::$_accounts->store();
2998
2999
			/**
3000
			 * IMPORTANT:
3001
			 *  Clear crons must be executed before clearing all storage.
3002
			 *  Otherwise, the cron will not be cleared.
3003
			 */
3004
			$this->clear_sync_cron();
3005
			$this->clear_install_sync_cron();
3006
3007
			// Clear all storage data.
3008
			$this->_storage->clear_all( true, array(
3009
				'connectivity_test',
3010
				'is_on',
3011
			) );
3012
3013
			// Send delete event.
3014
			$this->get_api_site_scope()->call( '/', 'delete' );
3015
3016
			$this->do_action( 'after_account_delete' );
3017
		}
3018
3019
		/**
3020
		 * Plugin deactivation hook.
3021
		 *
3022
		 * @author Vova Feldman (@svovaf)
3023
		 * @since  1.0.1
3024
		 */
3025
		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...
3026
			$this->_logger->entrance( 'slug = ' . $this->_slug );
3027
3028
			if ( ! current_user_can( 'activate_plugins' ) ) {
3029
				return;
3030
			}
3031
3032
			$this->_admin_notices->clear_all_sticky();
3033
			if ( isset( $this->_storage->sticky_optin_added ) ) {
3034
				unset( $this->_storage->sticky_optin_added );
3035
			}
3036
3037
			if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
3038
				// Remember that plugin was already installed.
3039
				$this->_storage->is_plugin_new_install = false;
0 ignored issues
show
Documentation introduced by
The property is_plugin_new_install 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...
3040
			}
3041
3042
			$this->clear_sync_cron();
3043
			$this->clear_install_sync_cron();
3044
3045
			if ( $this->is_registered() ) {
3046
				// Send deactivation event.
3047
				$this->sync_install( array(
3048
					'is_active' => false,
3049
				) );
3050
			} else {
3051
				if ( ! $this->has_api_connectivity() ) {
3052
					// Reset connectivity test cache.
3053
					unset( $this->_storage->connectivity_test );
3054
				}
3055
			}
3056
3057
			// Clear API cache on deactivation.
3058
			FS_Api::clear_cache();
3059
3060
			$this->remove_sdk_reference();
3061
		}
3062
3063
		/**
3064
		 * @author Vova Feldman (@svovaf)
3065
		 * @since  1.1.6
3066
		 */
3067
		private function remove_sdk_reference() {
3068
			global $fs_active_plugins;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
3069
3070
			foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) {
3071
				if ( $this->_plugin_basename == $data->plugin_path ) {
3072
					unset( $fs_active_plugins->plugins[ $sdk_path ] );
3073
					break;
3074
				}
3075
			}
3076
3077
			fs_fallback_to_newest_active_sdk();
3078
		}
3079
3080
		/**
3081
		 * @author Vova Feldman (@svovaf)
3082
		 * @since  1.1.3
3083
		 *
3084
		 * @param bool $is_anonymous
3085
		 */
3086
		private function set_anonymous_mode( $is_anonymous = true ) {
3087
			// Store information regarding skip to try and opt-in the user
3088
			// again in the future.
3089
			$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...
3090
				'is'        => $is_anonymous,
3091
				'timestamp' => WP_FS__SCRIPT_START_TIME,
3092
				'version'   => $this->get_plugin_version(),
3093
			);
3094
3095
			// Update anonymous mode cache.
3096
			$this->_is_anonymous = $is_anonymous;
3097
		}
3098
3099
		/**
3100
		 * @author Vova Feldman (@svovaf)
3101
		 * @since  1.1.3
3102
		 */
3103
		private function reset_anonymous_mode() {
3104
			unset( $this->_storage->is_anonymous );
3105
		}
3106
3107
		/**
3108
		 * Clears the anonymous mode and redirects to the opt-in screen.
3109
		 *
3110
		 * @author Vova Feldman (@svovaf)
3111
		 * @since  1.1.7
3112
		 */
3113
		function connect_again() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3114
			if ( ! $this->is_anonymous() ) {
3115
				return;
3116
			}
3117
3118
			$this->reset_anonymous_mode();
3119
3120
			if ( fs_redirect( $this->get_activation_url() ) ) {
3121
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method connect_again() 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...
3122
			}
3123
		}
3124
3125
		/**
3126
		 * Skip account connect, and set anonymous mode.
3127
		 *
3128
		 * @author Vova Feldman (@svovaf)
3129
		 * @since  1.1.1
3130
		 */
3131
		private function skip_connection() {
3132
			$this->_logger->entrance();
3133
3134
			$this->_admin_notices->remove_sticky( 'connect_account' );
3135
3136
			$this->set_anonymous_mode();
3137
3138
			// Send anonymous skip event.
3139
			// No user identified info nor any tracking will be sent after the user skips the opt-in.
3140
			$this->get_api_plugin_scope()->call( 'skip.json', 'put', array(
3141
				'uid' => $this->get_anonymous_id(),
3142
			) );
3143
		}
3144
3145
		/**
3146
		 * Plugin version update hook.
3147
		 *
3148
		 * @author Vova Feldman (@svovaf)
3149
		 * @since  1.0.4
3150
		 */
3151
		private function update_plugin_version_event() {
3152
			$this->_logger->entrance();
3153
3154
			if ( ! $this->is_registered() ) {
3155
				return;
3156
			}
3157
3158
			$this->schedule_install_sync();
3159
//			$this->sync_install( array(), true );
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
3160
		}
3161
3162
		/**
3163
		 * Update install details.
3164
		 *
3165
		 * @author Vova Feldman (@svovaf)
3166
		 * @since  1.1.2
3167
		 *
3168
		 * @param string[] string $override
3169
		 *
3170
		 * @return array
3171
		 */
3172
		private function get_install_data_for_api( $override = array() ) {
3173
			return array_merge( array(
3174
				'version'                      => $this->get_plugin_version(),
3175
				'is_premium'                   => $this->is_premium(),
3176
				'language'                     => get_bloginfo( 'language' ),
3177
				'charset'                      => get_bloginfo( 'charset' ),
3178
				'platform_version'             => get_bloginfo( 'version' ),
3179
				'programming_language_version' => phpversion(),
3180
				'title'                        => get_bloginfo( 'name' ),
3181
				'url'                          => get_site_url(),
3182
				// Special params.
3183
				'is_active'                    => true,
3184
				'is_uninstalled'               => false,
3185
			), $override );
3186
		}
3187
3188
		/**
3189
		 * Update install only if changed.
3190
		 *
3191
		 * @author Vova Feldman (@svovaf)
3192
		 * @since  1.0.9
3193
		 *
3194
		 * @param string[] string $override
3195
		 * @param bool     $flush
3196
		 *
3197
		 * @return false|object|string
3198
		 */
3199
		private function send_install_update( $override = array(), $flush = false ) {
3200
			$this->_logger->entrance();
3201
3202
			$check_properties = $this->get_install_data_for_api( $override );
3203
3204
			if ( $flush ) {
3205
				$params = $check_properties;
3206
			} else {
3207
				$params           = array();
3208
				$special          = array();
3209
				$special_override = false;
3210
3211
				foreach ( $check_properties as $p => $v ) {
3212
					if ( property_exists( $this->_site, $p ) ) {
3213
						if ( ! empty( $this->_site->{$p} ) &&
3214
						     $this->_site->{$p} != $v
3215
						) {
3216
							$this->_site->{$p} = $v;
3217
							$params[ $p ]      = $v;
3218
						}
3219
					} else {
3220
						$special[ $p ] = $v;
3221
3222
						if ( isset( $override[ $p ] ) ) {
3223
							$special_override = true;
3224
						}
3225
					}
3226
				}
3227
3228
				if ( $special_override || 0 < count( $params ) ) {
3229
					// Add special params only if has at least one
3230
					// standard param, or if explicitly requested to
3231
					// override a special param or a pram which is not exist
3232
					// in the install object.
3233
					$params = array_merge( $params, $special );
3234
				}
3235
			}
3236
3237
			if ( 0 < count( $params ) ) {
3238
				// Update last install sync timestamp.
3239
				$this->_storage->install_sync_timestamp = time();
0 ignored issues
show
Documentation introduced by
The property install_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...
3240
3241
				// Send updated values to FS.
3242
				$site = $this->get_api_site_scope()->call( '/', 'put', $params );
3243
3244
				if ( ! $this->is_api_error( $site ) ) {
3245
					// I successfully sent install update, clear scheduled sync if exist.
3246
					$this->clear_install_sync_cron();
3247
				}
3248
3249
				return $site;
3250
			}
3251
3252
			return false;
3253
		}
3254
3255
		/**
3256
		 * Update install only if changed.
3257
		 *
3258
		 * @author Vova Feldman (@svovaf)
3259
		 * @since  1.0.9
3260
		 *
3261
		 * @param string[] string $override
3262
		 * @param bool     $flush
3263
		 *
3264
		 * @return false|object|string
3265
		 */
3266
		private function sync_install( $override = array(), $flush = false ) {
3267
			$this->_logger->entrance();
3268
3269
			$site = $this->send_install_update( $override, $flush );
3270
3271
			if ( false === $site ) {
3272
				// No sync required.
3273
				return;
3274
			}
3275
3276
			if ( $this->is_api_error( $site ) ) {
3277
				// Failed to sync, don't update locally.
3278
				return;
3279
			}
3280
3281
			$plan              = $this->get_plan();
3282
			$this->_site       = new FS_Site( $site );
3283
			$this->_site->plan = $plan;
3284
3285
			$this->_store_site( true );
3286
		}
3287
3288
		/**
3289
		 * Plugin uninstall hook.
3290
		 *
3291
		 * @author Vova Feldman (@svovaf)
3292
		 * @since  1.0.1
3293
		 *
3294
		 * @param bool $check_user Enforce checking if user have plugins activation privileges.
3295
		 */
3296
		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...
3297
			$this->_logger->entrance( 'slug = ' . $this->_slug );
3298
3299
			if ( $check_user && ! current_user_can( 'activate_plugins' ) ) {
3300
				return;
3301
			}
3302
3303
			$params = array();
3304
			if ( isset( $this->_storage->uninstall_reason ) ) {
3305
				$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...
3306
				$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...
3307
			}
3308
3309
			if ( ! $this->is_registered() && isset( $this->_storage->uninstall_reason ) ) {
3310
				// Send anonymous uninstall event only if user submitted a feedback.
3311
				$params['uid'] = $this->get_anonymous_id();
3312
				$this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params );
3313
			} else {
3314
				// Send uninstall event.
3315
				$this->send_install_update( array_merge( $params, array(
3316
					'is_active'      => false,
3317
					'is_uninstalled' => true,
3318
				) ) );
3319
			}
3320
3321
			// @todo Decide if we want to delete plugin information from db.
3322
		}
3323
3324
		/**
3325
		 * @author Vova Feldman (@svovaf)
3326
		 * @since  1.1.1
3327
		 *
3328
		 * @return string
3329
		 */
3330
		private function premium_plugin_basename() {
3331
			return preg_replace( '/\//', '-premium/', $this->_free_plugin_basename, 1 );
3332
		}
3333
3334
		/**
3335
		 * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking.
3336
		 *
3337
		 * @author Vova Feldman (@svovaf)
3338
		 * @since  1.0.2
3339
		 */
3340
		public static function _uninstall_plugin_hook() {
3341
			self::_load_required_static();
3342
3343
			self::$_static_logger->entrance();
3344
3345
			if ( ! current_user_can( 'activate_plugins' ) ) {
3346
				return;
3347
			}
3348
3349
			$plugin_file = substr( current_filter(), strlen( 'uninstall_' ) );
3350
3351
			self::$_static_logger->info( 'plugin = ' . $plugin_file );
3352
3353
			define( 'WP_FS__UNINSTALL_MODE', true );
3354
3355
			$fs = self::get_instance_by_file( $plugin_file );
3356
3357
			if ( is_object( $fs ) ) {
3358
				self::require_plugin_essentials();
3359
3360
				if ( is_plugin_active( $fs->_free_plugin_basename ) ||
3361
				     is_plugin_active( $fs->premium_plugin_basename() )
3362
				) {
3363
					// Deleting Free or Premium plugin version while the other version still installed.
3364
					return;
3365
				}
3366
3367
				$fs->_uninstall_plugin_event();
3368
3369
				$fs->do_action( 'after_uninstall' );
3370
			}
3371
		}
3372
3373
		#region Plugin Information ------------------------------------------------------------------
3374
3375
		/**
3376
		 * Load WordPress core plugin.php essential module.
3377
		 *
3378
		 * @author Vova Feldman (@svovaf)
3379
		 * @since  1.1.1
3380
		 */
3381
		private static function require_plugin_essentials() {
3382
			if ( ! function_exists( 'get_plugins' ) ) {
3383
				require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
3384
			}
3385
		}
3386
3387
		/**
3388
		 * Load WordPress core pluggable.php module.
3389
		 *
3390
		 * @author Vova Feldman (@svovaf)
3391
		 * @since  1.1.2
3392
		 */
3393
		private static function require_pluggable_essentials() {
3394
			if ( ! function_exists( 'wp_get_current_user' ) ) {
3395
				require_once( ABSPATH . 'wp-includes/pluggable.php' );
3396
			}
3397
		}
3398
3399
		/**
3400
		 * Return plugin data.
3401
		 *
3402
		 * @author Vova Feldman (@svovaf)
3403
		 * @since  1.0.1
3404
		 *
3405
		 * @return array
3406
		 */
3407
		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...
3408
			if ( ! isset( $this->_plugin_data ) ) {
3409
				self::require_plugin_essentials();
3410
3411
				$this->_plugin_data = get_plugin_data( $this->_plugin_main_file_path );
3412
			}
3413
3414
			return $this->_plugin_data;
3415
		}
3416
3417
		/**
3418
		 * @author Vova Feldman (@svovaf)
3419
		 * @since  1.0.1
3420
		 *
3421
		 * @return string Plugin slug.
3422
		 */
3423
		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...
3424
			return $this->_slug;
3425
		}
3426
3427
		/**
3428
		 * @author Vova Feldman (@svovaf)
3429
		 * @since  1.0.1
3430
		 *
3431
		 * @return number Plugin ID.
3432
		 */
3433
		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...
3434
			return $this->_plugin->id;
3435
		}
3436
3437
		/**
3438
		 * @author Vova Feldman (@svovaf)
3439
		 * @since  1.0.1
3440
		 *
3441
		 * @return string Plugin public key.
3442
		 */
3443
		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...
3444
			return $this->_plugin->public_key;
3445
		}
3446
3447
		/**
3448
		 * Will be available only on sandbox mode.
3449
		 *
3450
		 * @author Vova Feldman (@svovaf)
3451
		 * @since  1.0.4
3452
		 *
3453
		 * @return mixed Plugin secret key.
3454
		 */
3455
		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...
3456
			return $this->_plugin->secret_key;
3457
		}
3458
3459
		/**
3460
		 * @author Vova Feldman (@svovaf)
3461
		 * @since  1.1.1
3462
		 *
3463
		 * @return bool
3464
		 */
3465
		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...
3466
			return ! empty( $this->_plugin->secret_key );
3467
		}
3468
3469
		/**
3470
		 * @author Vova Feldman (@svovaf)
3471
		 * @since  1.0.9
3472
		 *
3473
		 * @return string
3474
		 */
3475
		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...
3476
			$this->_logger->entrance();
3477
3478
			if ( ! isset( $this->_plugin_name ) ) {
3479
				$plugin_data = $this->get_plugin_data();
3480
3481
				// Get name.
3482
				$this->_plugin_name = $plugin_data['Name'];
3483
3484
				// Check if plugin name contains [Premium] suffix and remove it.
3485
				$suffix     = '[premium]';
3486
				$suffix_len = strlen( $suffix );
3487
3488
				if ( strlen( $plugin_data['Name'] ) > $suffix_len &&
3489
				     $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len )
3490
				) {
3491
					$this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len );
3492
				}
3493
3494
				$this->_logger->departure( 'Name = ' . $this->_plugin_name );
3495
			}
3496
3497
			return $this->_plugin_name;
3498
		}
3499
3500
		/**
3501
		 * @author Vova Feldman (@svovaf)
3502
		 * @since  1.0.0
3503
		 *
3504
		 * @return string
3505
		 */
3506
		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...
3507
			$this->_logger->entrance();
3508
3509
			$plugin_data = $this->get_plugin_data();
3510
3511
			$this->_logger->departure( 'Version = ' . $plugin_data['Version'] );
3512
3513
			return $plugin_data['Version'];
3514
		}
3515
3516
		/**
3517
		 * @author Vova Feldman (@svovaf)
3518
		 * @since  1.0.4
3519
		 *
3520
		 * @return string
3521
		 */
3522
		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...
3523
			return $this->_plugin_basename;
3524
		}
3525
3526
		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...
3527
			$this->_logger->entrance();
3528
3529
			$plugin_folder = $this->_plugin_basename;
3530
3531
			while ( '.' !== dirname( $plugin_folder ) ) {
3532
				$plugin_folder = dirname( $plugin_folder );
3533
			}
3534
3535
			$this->_logger->departure( 'Folder Name = ' . $plugin_folder );
3536
3537
			return $plugin_folder;
3538
		}
3539
3540
		#endregion ------------------------------------------------------------------
3541
3542
		/* Account
3543
		------------------------------------------------------------------------------------------------------------------*/
3544
3545
		/**
3546
		 * Find plugin's slug by plugin's basename.
3547
		 *
3548
		 * @author Vova Feldman (@svovaf)
3549
		 * @since  1.0.9
3550
		 *
3551
		 * @param string $plugin_base_name
3552
		 *
3553
		 * @return false|string
3554
		 */
3555
		private static function find_slug_by_basename( $plugin_base_name ) {
3556
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
3557
3558
			if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) {
3559
				return false;
3560
			}
3561
3562
			return $file_slug_map[ $plugin_base_name ];
3563
		}
3564
3565
		/**
3566
		 * Store the map between the plugin's basename to the slug.
3567
		 *
3568
		 * @author Vova Feldman (@svovaf)
3569
		 * @since  1.0.9
3570
		 */
3571
		private function store_file_slug_map() {
3572
			$file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() );
3573
3574
			if ( ! array( $file_slug_map ) ) {
3575
				$file_slug_map = array();
3576
			}
3577
3578
			if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) ||
3579
			     $file_slug_map[ $this->_plugin_basename ] !== $this->_slug
3580
			) {
3581
				$file_slug_map[ $this->_plugin_basename ] = $this->_slug;
3582
				self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true );
3583
			}
3584
		}
3585
3586
		/**
3587
		 * @return FS_User[]
3588
		 */
3589
		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...
3590
			$users = self::$_accounts->get_option( 'users', array() );
3591
3592
			if ( ! is_array( $users ) ) {
3593
				$users = array();
3594
			}
3595
3596
			return $users;
3597
		}
3598
3599
		/**
3600
		 * @return FS_Site[]
3601
		 */
3602
		private static function get_all_sites() {
3603
			$sites = self::$_accounts->get_option( 'sites', array() );
3604
3605
			if ( ! is_array( $sites ) ) {
3606
				$sites = array();
3607
			}
3608
3609
			return $sites;
3610
		}
3611
3612
		/**
3613
		 * @author Vova Feldman (@svovaf)
3614
		 * @since  1.0.6
3615
		 *
3616
		 * @return FS_Plugin_License[]
3617
		 */
3618
		private static function get_all_licenses() {
3619
			$licenses = self::$_accounts->get_option( 'licenses', array() );
3620
3621
			if ( ! is_array( $licenses ) ) {
3622
				$licenses = array();
3623
			}
3624
3625
			return $licenses;
3626
		}
3627
3628
		/**
3629
		 * @return FS_Plugin_Plan[]
3630
		 */
3631
		private static function get_all_plans() {
3632
			$plans = self::$_accounts->get_option( 'plans', array() );
3633
3634
			if ( ! is_array( $plans ) ) {
3635
				$plans = array();
3636
			}
3637
3638
			return $plans;
3639
		}
3640
3641
		/**
3642
		 * @author Vova Feldman (@svovaf)
3643
		 * @since  1.0.4
3644
		 *
3645
		 * @return FS_Plugin_Tag[]
3646
		 */
3647
		private static function get_all_updates() {
3648
			$updates = self::$_accounts->get_option( 'updates', array() );
3649
3650
			if ( ! is_array( $updates ) ) {
3651
				$updates = array();
3652
			}
3653
3654
			return $updates;
3655
		}
3656
3657
		/**
3658
		 * @author Vova Feldman (@svovaf)
3659
		 * @since  1.0.6
3660
		 *
3661
		 * @return FS_Plugin[]|false
3662
		 */
3663
		private static function get_all_addons() {
3664
			$addons = self::$_accounts->get_option( 'addons', array() );
3665
3666
			if ( ! is_array( $addons ) ) {
3667
				$addons = array();
3668
			}
3669
3670
			return $addons;
3671
		}
3672
3673
		/**
3674
		 * @author Vova Feldman (@svovaf)
3675
		 * @since  1.0.6
3676
		 *
3677
		 * @return FS_Plugin[]|false
3678
		 */
3679
		private static function get_all_account_addons() {
3680
			$addons = self::$_accounts->get_option( 'account_addons', array() );
3681
3682
			if ( ! is_array( $addons ) ) {
3683
				$addons = array();
3684
			}
3685
3686
			return $addons;
3687
		}
3688
3689
		/**
3690
		 * Check if user is registered.
3691
		 *
3692
		 * @author Vova Feldman (@svovaf)
3693
		 * @since  1.0.1
3694
		 * @return bool
3695
		 */
3696
		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...
3697
			return is_object( $this->_user );
3698
		}
3699
3700
		/**
3701
		 * @author Vova Feldman (@svovaf)
3702
		 * @since  1.0.4
3703
		 *
3704
		 * @return FS_Plugin
3705
		 */
3706
		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...
3707
			return $this->_plugin;
3708
		}
3709
3710
		/**
3711
		 * @author Vova Feldman (@svovaf)
3712
		 * @since  1.0.3
3713
		 *
3714
		 * @return FS_User
3715
		 */
3716
		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...
3717
			return $this->_user;
3718
		}
3719
3720
		/**
3721
		 * @author Vova Feldman (@svovaf)
3722
		 * @since  1.0.3
3723
		 *
3724
		 * @return FS_Site
3725
		 */
3726
		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...
3727
			return $this->_site;
3728
		}
3729
3730
		/**
3731
		 * Get plugin add-ons.
3732
		 *
3733
		 * @author Vova Feldman (@svovaf)
3734
		 * @since  1.0.6
3735
		 *
3736
		 * @since  1.1.7.3 If not yet loaded, fetch data from the API.
3737
		 *
3738
		 * @return FS_Plugin[]|false
3739
		 */
3740
		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...
3741
			$this->_logger->entrance();
3742
3743
			$addons = self::get_all_addons();
3744
3745
			/**
3746
			 * @since 1.1.7.3 If not yet loaded, fetch data from the API.
3747
			 */
3748
			if ( ! is_array( $addons ) ||
3749
			     ! isset( $addons[ $this->_plugin->id ] ) ||
3750
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
3751
			     empty( $addons[ $this->_plugin->id ] )
3752
			) {
3753
				if ( $this->_has_addons ) {
3754
					$addons = $this->_sync_addons();
3755
				}
3756
			}
3757
3758
			if ( ! is_array( $addons ) ||
3759
			     ! isset( $addons[ $this->_plugin->id ] ) ||
3760
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
3761
			     empty( $addons[ $this->_plugin->id ] )
3762
			) {
3763
				return false;
3764
			}
3765
3766
			return $addons[ $this->_plugin->id ];
3767
		}
3768
3769
		/**
3770
		 * @author Vova Feldman (@svovaf)
3771
		 * @since  1.0.6
3772
		 *
3773
		 * @return FS_Plugin[]|false
3774
		 */
3775
		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...
3776
			$this->_logger->entrance();
3777
3778
			$addons = self::get_all_account_addons();
3779
3780
			if ( ! is_array( $addons ) ||
3781
			     ! isset( $addons[ $this->_plugin->id ] ) ||
3782
			     ! is_array( $addons[ $this->_plugin->id ] ) ||
3783
			     0 === count( $addons[ $this->_plugin->id ] )
3784
			) {
3785
				return false;
3786
			}
3787
3788
			return $addons[ $this->_plugin->id ];
3789
		}
3790
3791
		/**
3792
		 * Check if user has any
3793
		 *
3794
		 * @author Vova Feldman (@svovaf)
3795
		 * @since  1.1.6
3796
		 *
3797
		 * @return bool
3798
		 */
3799
		function has_account_addons() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3800
			$addons = $this->get_account_addons();
3801
3802
			return is_array( $addons ) && ( 0 < count( $addons ) );
3803
		}
3804
3805
3806
		/**
3807
		 * Get add-on by ID (from local data).
3808
		 *
3809
		 * @author Vova Feldman (@svovaf)
3810
		 * @since  1.0.6
3811
		 *
3812
		 * @param number $id
3813
		 *
3814
		 * @return FS_Plugin|false
3815
		 */
3816
		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...
3817
			$this->_logger->entrance();
3818
3819
			$addons = $this->get_addons();
3820
3821
			if ( is_array( $addons ) ) {
3822
				foreach ( $addons as $addon ) {
3823
					if ( $id == $addon->id ) {
3824
						return $addon;
3825
					}
3826
				}
3827
			}
3828
3829
			return false;
3830
		}
3831
3832
		/**
3833
		 * Get add-on by slug (from local data).
3834
		 *
3835
		 * @author Vova Feldman (@svovaf)
3836
		 * @since  1.0.6
3837
		 *
3838
		 * @param string $slug
3839
		 *
3840
		 * @return FS_Plugin|false
3841
		 */
3842
		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...
3843
			$this->_logger->entrance();
3844
3845
			$addons = $this->get_addons();
3846
3847
			if ( is_array( $addons ) ) {
3848
				foreach ( $addons as $addon ) {
3849
					if ( $slug == $addon->slug ) {
3850
						return $addon;
3851
					}
3852
				}
3853
			}
3854
3855
			return false;
3856
		}
3857
3858
		#region Plans & Licensing ------------------------------------------------------------------
3859
3860
		/**
3861
		 * Check if running premium plugin code.
3862
		 *
3863
		 * @author Vova Feldman (@svovaf)
3864
		 * @since  1.0.5
3865
		 *
3866
		 * @return bool
3867
		 */
3868
		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...
3869
			return $this->_plugin->is_premium;
3870
		}
3871
3872
		/**
3873
		 * Get site's plan ID.
3874
		 *
3875
		 * @author Vova Feldman (@svovaf)
3876
		 * @since  1.0.2
3877
		 *
3878
		 * @return number
3879
		 */
3880
		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...
3881
			return $this->_site->plan->id;
3882
		}
3883
3884
		/**
3885
		 * Get site's plan title.
3886
		 *
3887
		 * @author Vova Feldman (@svovaf)
3888
		 * @since  1.0.2
3889
		 *
3890
		 * @return string
3891
		 */
3892
		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...
3893
			return $this->_site->plan->title;
3894
		}
3895
3896
		/**
3897
		 * @author Vova Feldman (@svovaf)
3898
		 * @since  1.0.9
3899
		 *
3900
		 * @return FS_Plugin_Plan
3901
		 */
3902
		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...
3903
			return is_object( $this->_site->plan ) ? $this->_site->plan : false;
3904
		}
3905
3906
		/**
3907
		 * @author Vova Feldman (@svovaf)
3908
		 * @since  1.0.3
3909
		 *
3910
		 * @return bool
3911
		 */
3912
		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...
3913
			$this->_logger->entrance();
3914
3915
			if ( ! $this->is_registered() ) {
3916
				return false;
3917
			}
3918
3919
			return $this->_site->is_trial();
3920
		}
3921
3922
		/**
3923
		 * Check if currently in a trial with payment method (credit card or paypal).
3924
		 *
3925
		 * @author Vova Feldman (@svovaf)
3926
		 * @since  1.1.7
3927
		 *
3928
		 * @return bool
3929
		 */
3930
		function is_paid_trial() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
3931
			$this->_logger->entrance();
3932
3933
			if ( ! $this->is_trial() ) {
3934
				return false;
3935
			}
3936
3937
			return $this->has_active_license() && ( $this->_site->trial_plan_id == $this->_license->plan_id );
3938
		}
3939
3940
		/**
3941
		 * Check if trial already utilized.
3942
		 *
3943
		 * @since 1.0.9
3944
		 *
3945
		 * @return bool
3946
		 */
3947
		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...
3948
			$this->_logger->entrance();
3949
3950
			if ( ! $this->is_registered() ) {
3951
				return false;
3952
			}
3953
3954
			return $this->_site->is_trial_utilized();
3955
		}
3956
3957
		/**
3958
		 * Get trial plan information (if in trial).
3959
		 *
3960
		 * @author Vova Feldman (@svovaf)
3961
		 * @since  1.0.9
3962
		 *
3963
		 * @return bool|FS_Plugin_Plan
3964
		 */
3965
		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...
3966
			$this->_logger->entrance();
3967
3968
			if ( ! $this->is_trial() ) {
3969
				return false;
3970
			}
3971
3972
			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...
3973
		}
3974
3975
		/**
3976
		 * Check if the user has an activated and valid paid license on current plugin's install.
3977
		 *
3978
		 * @since 1.0.9
3979
		 *
3980
		 * @return bool
3981
		 */
3982
		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...
3983
			$this->_logger->entrance();
3984
3985
			if ( ! $this->is_registered() ) {
3986
				return false;
3987
			}
3988
3989
			if ( ! $this->has_paid_plan() ) {
3990
				return false;
3991
			}
3992
3993
			return (
3994
				! $this->is_trial() &&
3995
				'free' !== $this->_site->plan->name &&
3996
				$this->has_features_enabled_license()
3997
			);
3998
		}
3999
4000
		/**
4001
		 * @author Vova Feldman (@svovaf)
4002
		 * @since  1.0.4
4003
		 *
4004
		 * @return bool
4005
		 */
4006
		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...
4007
			if ( ! $this->is_registered() ) {
4008
				return true;
4009
			}
4010
4011
			if ( ! $this->has_paid_plan() ) {
4012
				return true;
4013
			}
4014
4015
			return (
4016
				'free' === $this->_site->plan->name ||
4017
				! $this->has_features_enabled_license()
4018
			);
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 4018 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...
4019
		}
4020
4021
		/**
4022
		 * @author Vova Feldman (@svovaf)
4023
		 * @since  1.0.5
4024
		 *
4025
		 * @return bool
4026
		 */
4027
		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...
4028
			$this->_logger->entrance();
4029
4030
			$premium_license = $this->_get_available_premium_license();
4031
4032
			return ( false !== $premium_license );
4033
		}
4034
4035
		/**
4036
		 * Check if user has any licenses associated with the plugin (including expired or blocking).
4037
		 *
4038
		 * @author Vova Feldman (@svovaf)
4039
		 * @since  1.1.7.3
4040
		 *
4041
		 * @return bool
4042
		 */
4043
		private function has_any_license() {
4044
			return is_array( $this->_licenses ) && ( 0 < count( $this->_licenses ) );
4045
		}
4046
4047
		/**
4048
		 * @author Vova Feldman (@svovaf)
4049
		 * @since  1.0.5
4050
		 *
4051
		 * @return FS_Plugin_License
4052
		 */
4053
		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...
4054
			$this->_logger->entrance();
4055
4056
			if ( ! $this->has_paid_plan() ) {
4057
				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...
4058
			}
4059
4060
			if ( is_array( $this->_licenses ) ) {
4061
				foreach ( $this->_licenses as $license ) {
4062
					if ( ! $license->is_utilized() && $license->is_features_enabled() ) {
4063
						return $license;
4064
					}
4065
				}
4066
			}
4067
4068
			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...
4069
		}
4070
4071
		/**
4072
		 * Sync local plugin plans with remote server.
4073
		 *
4074
		 * @author Vova Feldman (@svovaf)
4075
		 * @since  1.0.5
4076
		 *
4077
		 * @return FS_Plugin_Plan[]|object
4078
		 */
4079
		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...
4080
			$plans = $this->_fetch_plugin_plans();
4081
			if ( ! $this->is_api_error( $plans ) ) {
4082
				$this->_plans = $plans;
4083
				$this->_store_plans();
4084
			}
4085
4086
			$this->do_action( 'after_plans_sync', $plans );
4087
4088
			return $this->_plans;
4089
		}
4090
4091
		/**
4092
		 * @author Vova Feldman (@svovaf)
4093
		 * @since  1.0.5
4094
		 *
4095
		 * @param number $id
4096
		 *
4097
		 * @return FS_Plugin_Plan
4098
		 */
4099
		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...
4100
			$this->_logger->entrance();
4101
4102
			if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
4103
				$this->_sync_plans();
4104
			}
4105
4106
			foreach ( $this->_plans as $plan ) {
4107
				if ( $id == $plan->id ) {
4108
					return $plan;
4109
				}
4110
			}
4111
4112
			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...
4113
		}
4114
4115
		/**
4116
		 * Sync local plugin plans with remote server.
4117
		 *
4118
		 * @author Vova Feldman (@svovaf)
4119
		 * @since  1.0.6
4120
		 *
4121
		 * @return FS_Plugin_License[]|object
4122
		 */
4123
		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...
4124
			$licenses = $this->_fetch_licenses();
4125
			if ( ! $this->is_api_error( $licenses ) ) {
4126
				$this->_licenses = $licenses;
4127
				$this->_store_licenses();
4128
			}
4129
4130
			// Update current license.
4131
			if ( is_object( $this->_license ) ) {
4132
				$this->_license = $this->_get_license_by_id( $this->_license->id );
4133
			}
4134
4135
			return $this->_licenses;
4136
		}
4137
4138
		/**
4139
		 * @author Vova Feldman (@svovaf)
4140
		 * @since  1.0.5
4141
		 *
4142
		 * @param number $id
4143
		 *
4144
		 * @return FS_Plugin_License
4145
		 */
4146
		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...
4147
			$this->_logger->entrance();
4148
4149
			if ( ! is_numeric( $id ) ) {
4150
				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...
4151
			}
4152
4153
			if ( ! is_array( $this->_licenses ) || 0 === count( $this->_licenses ) ) {
4154
				$this->_sync_licenses();
4155
			}
4156
4157
			foreach ( $this->_licenses as $license ) {
4158
				if ( $id == $license->id ) {
4159
					return $license;
4160
				}
4161
			}
4162
4163
			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...
4164
		}
4165
4166
		/**
4167
		 * Sync site's license with user licenses.
4168
		 *
4169
		 * @author Vova Feldman (@svovaf)
4170
		 * @since  1.0.6
4171
		 *
4172
		 * @param FS_Plugin_License|null $new_license
4173
		 */
4174
		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...
4175
			$this->_logger->entrance();
4176
4177
			$this->_license = $new_license;
4178
4179
			if ( ! is_object( $new_license ) ) {
4180
				$this->_site->license_id = null;
4181
				$this->_sync_site_subscription( null );
4182
4183
				return;
4184
			}
4185
4186
			$this->_site->license_id = $this->_license->id;
4187
4188
			if ( ! is_array( $this->_licenses ) ) {
4189
				$this->_licenses = array();
4190
			}
4191
4192
			$is_license_found = false;
4193
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
4194
				if ( $new_license->id == $this->_licenses[ $i ]->id ) {
4195
					$this->_licenses[ $i ] = $new_license;
4196
4197
					$is_license_found = true;
4198
					break;
4199
				}
4200
			}
4201
4202
			// If new license just append.
4203
			if ( ! $is_license_found ) {
4204
				$this->_licenses[] = $new_license;
4205
			}
4206
4207
			$this->_sync_site_subscription( $new_license );
4208
		}
4209
4210
		/**
4211
		 * Sync site's subscription.
4212
		 *
4213
		 * @author Vova Feldman (@svovaf)
4214
		 * @since  1.0.9
4215
		 *
4216
		 * @param FS_Plugin_License|null $license
4217
		 *
4218
		 * @return bool|\FS_Subscription
4219
		 */
4220
		private function _sync_site_subscription( $license ) {
4221
			if ( ! is_object( $license ) ) {
4222
				unset( $this->_storage->subscription );
4223
4224
				return false;
4225
			}
4226
4227
			// Load subscription details if not lifetime.
4228
			$subscription = $license->is_lifetime() ?
4229
				false :
4230
				$this->_fetch_site_license_subscription();
4231
4232
			if ( is_object( $subscription ) && ! isset( $subscription->error ) ) {
4233
				$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...
4234
			} else {
4235
				unset( $this->_storage->subscription );
4236
			}
4237
4238
			return $subscription;
4239
		}
4240
4241
		/**
4242
		 * @author Vova Feldman (@svovaf)
4243
		 * @since  1.0.6
4244
		 *
4245
		 * @return bool|\FS_Plugin_License
4246
		 */
4247
		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...
4248
			return $this->_license;
4249
		}
4250
4251
		/**
4252
		 * @return bool|\FS_Subscription
4253
		 */
4254
		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...
4255
			return isset( $this->_storage->subscription ) ?
4256
				$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...
4257
				false;
4258
		}
4259
4260
		/**
4261
		 * @author Vova Feldman (@svovaf)
4262
		 * @since  1.0.2
4263
		 *
4264
		 * @param string $plan  Plan name
4265
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
4266
		 *
4267
		 * @return bool
4268
		 */
4269
		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...
4270
			$this->_logger->entrance();
4271
4272
			if ( ! $this->is_registered() ) {
4273
				return false;
4274
			}
4275
4276
			$plan = strtolower( $plan );
4277
4278
			if ( $this->_site->plan->name === $plan ) // Exact plan.
4279
			{
4280
				return true;
4281
			} else if ( $exact ) // Required exact, but plans are different.
4282
			{
4283
				return false;
4284
			}
4285
4286
			$current_plan_order  = - 1;
4287
			$required_plan_order = - 1;
4288
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
4289
				if ( $plan === $this->_plans[ $i ]->name ) {
4290
					$required_plan_order = $i;
4291
				} else if ( $this->_site->plan->name === $this->_plans[ $i ]->name ) {
4292
					$current_plan_order = $i;
4293
				}
4294
			}
4295
4296
			return ( $current_plan_order > $required_plan_order );
4297
		}
4298
4299
		/**
4300
		 * Check if plan based on trial. If not in trial mode, should return false.
4301
		 *
4302
		 * @since  1.0.9
4303
		 *
4304
		 * @param string $plan  Plan name
4305
		 * @param bool   $exact If true, looks for exact plan. If false, also check "higher" plans.
4306
		 *
4307
		 * @return bool
4308
		 */
4309
		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...
4310
			$this->_logger->entrance();
4311
4312
			if ( ! $this->is_registered() ) {
4313
				return false;
4314
			}
4315
4316
			if ( ! $this->is_trial() ) {
4317
				return false;
4318
			}
4319
4320
			if ( ! isset( $this->_storage->trial_plan ) ) {
4321
				// Store trial plan information.
4322
				$this->_enrich_site_trial_plan( true );
4323
			}
4324
4325
			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...
4326
			{
4327
				return true;
4328
			} else if ( $exact ) // Required exact, but plans are different.
4329
			{
4330
				return false;
4331
			}
4332
4333
			$current_plan_order  = - 1;
4334
			$required_plan_order = - 1;
4335
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
4336
				if ( $plan === $this->_plans[ $i ]->name ) {
4337
					$required_plan_order = $i;
4338
				} 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...
4339
					$current_plan_order = $i;
4340
				}
4341
			}
4342
4343
			return ( $current_plan_order > $required_plan_order );
4344
		}
4345
4346
		/**
4347
		 * Check if plugin has any paid plans.
4348
		 *
4349
		 * @author Vova Feldman (@svovaf)
4350
		 * @since  1.0.7
4351
		 *
4352
		 * @return bool
4353
		 */
4354
		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...
4355
			return $this->_has_paid_plans || FS_Plan_Manager::instance()->has_paid_plan( $this->_plans );
4356
		}
4357
4358
		/**
4359
		 * Check if plugin has any plan with a trail.
4360
		 *
4361
		 * @author Vova Feldman (@svovaf)
4362
		 * @since  1.0.9
4363
		 *
4364
		 * @return bool
4365
		 */
4366
		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...
4367
			if ( ! $this->is_registered() ) {
4368
				return false;
4369
			}
4370
4371
			return $this->_storage->get( 'has_trial_plan', false );
4372
		}
4373
4374
		/**
4375
		 * Check if plugin has any free plan, or is it premium only.
4376
		 *
4377
		 * Note: If no plans configured, assume plugin is free.
4378
		 *
4379
		 * @author Vova Feldman (@svovaf)
4380
		 * @since  1.0.7
4381
		 *
4382
		 * @return bool
4383
		 */
4384
		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...
4385
			return FS_Plan_Manager::instance()->has_free_plan( $this->_plans );
4386
		}
4387
4388
		#region URL Generators
4389
4390
		/**
4391
		 * Alias to pricing_url().
4392
		 *
4393
		 * @author Vova Feldman (@svovaf)
4394
		 * @since  1.0.2
4395
		 *
4396
		 * @uses   pricing_url()
4397
		 *
4398
		 * @param string $period Billing cycle
4399
		 * @param bool   $is_trial
4400
		 *
4401
		 * @return string
4402
		 */
4403
		function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4404
			return $this->pricing_url( $period, $is_trial );
4405
		}
4406
4407
		/**
4408
		 * @author Vova Feldman (@svovaf)
4409
		 * @since  1.0.9
4410
		 *
4411
		 * @uses   get_upgrade_url()
4412
		 *
4413
		 * @return string
4414
		 */
4415
		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...
4416
			return $this->get_upgrade_url( WP_FS__PERIOD_ANNUALLY, true );
4417
		}
4418
4419
		/**
4420
		 * Plugin's pricing URL.
4421
		 *
4422
		 * @author Vova Feldman (@svovaf)
4423
		 * @since  1.0.4
4424
		 *
4425
		 * @param string $billing_cycle Billing cycle
4426
		 *
4427
		 * @param bool   $is_trial
4428
		 *
4429
		 * @return string
4430
		 */
4431
		function pricing_url( $billing_cycle = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4432
			$this->_logger->entrance();
4433
4434
			$params = array(
4435
				'billing_cycle' => $billing_cycle
4436
			);
4437
4438
			if ( $is_trial ) {
4439
				$params['trial'] = 'true';
4440
			}
4441
4442
			return $this->_get_admin_page_url( 'pricing', $params );
4443
		}
4444
4445
		/**
4446
		 * Checkout page URL.
4447
		 *
4448
		 * @author   Vova Feldman (@svovaf)
4449
		 * @since    1.0.6
4450
		 *
4451
		 * @param string $billing_cycle Billing cycle
4452
		 * @param bool   $is_trial
4453
		 * @param array  $extra         (optional) Extra parameters, override other query params.
4454
		 *
4455
		 * @return string
4456
		 */
4457
		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...
4458
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
4459
			$is_trial = false,
4460
			$extra = array()
4461
		) {
4462
			$this->_logger->entrance();
4463
4464
			$params = array(
4465
				'checkout'      => 'true',
4466
				'billing_cycle' => $billing_cycle,
4467
			);
4468
4469
			if ( $is_trial ) {
4470
				$params['trial'] = 'true';
4471
			}
4472
4473
			/**
4474
			 * Params in extra override other params.
4475
			 */
4476
			$params = array_merge( $params, $extra );
4477
4478
			return $this->_get_admin_page_url( 'pricing', $params );
4479
		}
4480
4481
		/**
4482
		 * Add-on checkout URL.
4483
		 *
4484
		 * @author   Vova Feldman (@svovaf)
4485
		 * @since    1.1.7
4486
		 *
4487
		 * @param number $addon_id
4488
		 * @param number $pricing_id
4489
		 * @param string $billing_cycle
4490
		 * @param bool   $is_trial
4491
		 *
4492
		 * @return string
4493
		 */
4494
		function addon_checkout_url(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4495
			$addon_id,
4496
			$pricing_id,
4497
			$billing_cycle = WP_FS__PERIOD_ANNUALLY,
4498
			$is_trial = false
4499
		) {
4500
			return $this->checkout_url( $billing_cycle, $is_trial, array(
4501
				'plugin_id'  => $addon_id,
4502
				'pricing_id' => $pricing_id,
4503
			) );
4504
		}
4505
4506
		#endregion
4507
4508
		#endregion ------------------------------------------------------------------
4509
4510
		/**
4511
		 * Check if plugin has any add-ons.
4512
		 *
4513
		 * @author Vova Feldman (@svovaf)
4514
		 * @since  1.0.5
4515
		 *
4516
		 * @since  1.1.7.3 Base logic only on the parameter provided by the developer in the init function.
4517
		 *
4518
		 * @return bool
4519
		 */
4520
		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...
4521
			$this->_logger->entrance();
4522
4523
			return $this->_has_addons;
4524
		}
4525
4526
		/**
4527
		 * Check if plugin can work in anonymous mode.
4528
		 *
4529
		 * @author Vova Feldman (@svovaf)
4530
		 * @since  1.0.9
4531
		 *
4532
		 * @return bool
4533
		 */
4534
		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...
4535
			return $this->_enable_anonymous;
4536
		}
4537
4538
		/**
4539
		 * Check if feature supported with current site's plan.
4540
		 *
4541
		 * @author Vova Feldman (@svovaf)
4542
		 * @since  1.0.1
4543
		 *
4544
		 * @todo   IMPLEMENT
4545
		 *
4546
		 * @param number $feature_id
4547
		 *
4548
		 * @throws Exception
4549
		 */
4550
		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...
4551
			throw new Exception( 'not implemented' );
4552
		}
4553
4554
		/**
4555
		 * @author Vova Feldman (@svovaf)
4556
		 * @since  1.0.1
4557
		 *
4558
		 * @return bool Is running in SSL/HTTPS
4559
		 */
4560
		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...
4561
			return WP_FS__IS_HTTPS;
4562
		}
4563
4564
		/**
4565
		 * @author Vova Feldman (@svovaf)
4566
		 * @since  1.0.9
4567
		 *
4568
		 * @return bool Is running in AJAX call.
4569
		 *
4570
		 * @link   http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax
4571
		 */
4572
		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...
4573
			return ( defined( 'DOING_AJAX' ) && DOING_AJAX );
4574
		}
4575
4576
		/**
4577
		 * @author Vova Feldman (@svovaf)
4578
		 * @since  1.1.7
4579
		 *
4580
		 * @return bool
4581
		 */
4582
		function is_cron() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4583
			return ( defined( 'DOING_CRON' ) && DOING_CRON );
4584
		}
4585
4586
		/**
4587
		 * Check if a real user is visiting the admin dashboard.
4588
		 *
4589
		 * @author Vova Feldman (@svovaf)
4590
		 * @since  1.1.7
4591
		 *
4592
		 * @return bool
4593
		 */
4594
		function is_user_in_admin() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
4595
			return is_admin() && ! $this->is_ajax() && ! $this->is_cron();
4596
		}
4597
4598
		/**
4599
		 * Check if running in HTTPS and if site's plan matching the specified plan.
4600
		 *
4601
		 * @param string $plan
4602
		 * @param bool   $exact
4603
		 *
4604
		 * @return bool
4605
		 */
4606
		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...
4607
			return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) );
4608
		}
4609
4610
		/**
4611
		 * Construct plugin's settings page URL.
4612
		 *
4613
		 * @author Vova Feldman (@svovaf)
4614
		 * @since  1.0.4
4615
		 *
4616
		 * @param string $page
4617
		 * @param array  $params
4618
		 *
4619
		 * @return string
4620
		 */
4621
		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...
4622
			if ( ! $this->_menu->is_top_level() ) {
4623
				$parent_slug = $this->_menu->get_parent_slug();
4624
				$menu_file   = ( false !== strpos( $parent_slug, '.php' ) ) ?
4625
					$parent_slug :
4626
					'admin.php';
4627
4628
				return add_query_arg( array_merge( $params, array(
4629
					'page' => $this->_menu->get_slug( $page ),
4630
				) ), admin_url( $menu_file, 'admin' ) );
4631
			}
4632
4633
			if ( $this->_menu->is_cpt() ) {
4634
				if ( empty( $page ) && $this->is_activation_mode() ) {
4635
					return add_query_arg( array_merge( $params, array(
4636
						'page' => $this->_menu->get_slug()
4637
					) ), admin_url( 'admin.php', 'admin' ) );
4638
				} else {
4639
					if ( ! empty( $page ) ) {
4640
						$params['page'] = $this->_menu->get_slug( $page );
4641
					}
4642
4643
					return add_query_arg( $params, admin_url( $this->_menu->get_raw_slug(), 'admin' ) );
4644
				}
4645
			} else {
4646
				return add_query_arg( array_merge( $params, array(
4647
					'page' => $this->_menu->get_slug( $page ),
4648
				) ), admin_url( 'admin.php', 'admin' ) );
4649
			}
4650
		}
4651
4652
4653
		/**
4654
		 * Plugin's account URL.
4655
		 *
4656
		 * @author Vova Feldman (@svovaf)
4657
		 * @since  1.0.4
4658
		 *
4659
		 * @param bool|string $action
4660
		 * @param array       $params
4661
		 *
4662
		 * @param bool        $add_action_nonce
4663
		 *
4664
		 * @return string
4665
		 */
4666
		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...
4667
			if ( is_string( $action ) ) {
4668
				$params['fs_action'] = $action;
4669
			}
4670
4671
			self::require_pluggable_essentials();
4672
4673
			return ( $add_action_nonce && is_string( $action ) ) ?
4674
				wp_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) :
4675
				$this->_get_admin_page_url( 'account', $params );
4676
		}
4677
4678
		/**
4679
		 * Plugin's account URL.
4680
		 *
4681
		 * @author Vova Feldman (@svovaf)
4682
		 * @since  1.0.4
4683
		 *
4684
		 * @param bool|string $topic
4685
		 * @param bool|string $message
4686
		 *
4687
		 * @return string
4688
		 */
4689
		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...
4690
			$params = array();
4691
			if ( is_string( $topic ) ) {
4692
				$params['topic'] = $topic;
4693
			}
4694
			if ( is_string( $message ) ) {
4695
				$params['message'] = $message;
4696
			}
4697
4698
			if ( $this->is_addon() ) {
4699
				$params['addon_id'] = $this->get_id();
4700
4701
				return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params );
4702
			} else {
4703
				return $this->_get_admin_page_url( 'contact', $params );
4704
			}
4705
		}
4706
4707
		/**
4708
		 * Add-on direct info URL.
4709
		 *
4710
		 * @author Vova Feldman (@svovaf)
4711
		 * @since  1.1.0
4712
		 *
4713
		 * @param string $slug
4714
		 *
4715
		 * @return string
4716
		 */
4717
		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...
4718
			return $this->_get_admin_page_url( 'addons', array(
4719
				'slug' => $slug
4720
			) );
4721
		}
4722
4723
		/* Logger
4724
		------------------------------------------------------------------------------------------------------------------*/
4725
		/**
4726
		 * @param string $id
4727
		 * @param bool   $prefix_slug
4728
		 *
4729
		 * @return FS_Logger
4730
		 */
4731
		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...
4732
			return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id );
4733
		}
4734
4735
		/**
4736
		 * @param      $id
4737
		 * @param bool $load_options
4738
		 * @param bool $prefix_slug
4739
		 *
4740
		 * @return FS_Option_Manager
4741
		 */
4742
		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...
4743
			return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options );
4744
		}
4745
4746
		/* Security
4747
		------------------------------------------------------------------------------------------------------------------*/
4748
		private function _encrypt( $str ) {
4749
			if ( is_null( $str ) ) {
4750
				return null;
4751
			}
4752
4753
			return base64_encode( $str );
4754
		}
4755
4756
		private function _decrypt( $str ) {
4757
			if ( is_null( $str ) ) {
4758
				return null;
4759
			}
4760
4761
			return base64_decode( $str );
4762
		}
4763
4764
		/**
4765
		 * @author Vova Feldman (@svovaf)
4766
		 * @since  1.0.5
4767
		 *
4768
		 * @param FS_Entity $entity
4769
		 *
4770
		 * @return FS_Entity Return an encrypted clone entity.
4771
		 */
4772
		private function _encrypt_entity( FS_Entity $entity ) {
4773
			$clone = clone $entity;
4774
			$props = get_object_vars( $entity );
4775
4776
			foreach ( $props as $key => $val ) {
4777
				$clone->{$key} = $this->_encrypt( $val );
4778
			}
4779
4780
			return $clone;
4781
		}
4782
4783
		/**
4784
		 * @author Vova Feldman (@svovaf)
4785
		 * @since  1.0.5
4786
		 *
4787
		 * @param FS_Entity $entity
4788
		 *
4789
		 * @return FS_Entity Return an decrypted clone entity.
4790
		 */
4791
		private function _decrypt_entity( FS_Entity $entity ) {
4792
			$clone = clone $entity;
4793
			$props = get_object_vars( $entity );
4794
4795
			foreach ( $props as $key => $val ) {
4796
				$clone->{$key} = $this->_decrypt( $val );
4797
			}
4798
4799
			return $clone;
4800
		}
4801
4802
		/**
4803
		 * Tries to activate account based on POST params.
4804
		 *
4805
		 * @author Vova Feldman (@svovaf)
4806
		 * @since  1.0.2
4807
		 */
4808
		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...
4809
			if ( $this->is_registered() ) {
4810
				// Already activated.
4811
				return;
4812
			}
4813
4814
			self::_clean_admin_content_section();
4815
4816
			if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) {
4817
//				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...
4818
4819
				// Verify matching plugin details.
4820
				if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) {
4821
					return;
4822
				}
4823
4824
				$user              = new FS_User();
4825
				$user->id          = fs_request_get( 'user_id' );
4826
				$user->public_key  = fs_request_get( 'user_public_key' );
4827
				$user->secret_key  = fs_request_get( 'user_secret_key' );
4828
				$user->email       = fs_request_get( 'user_email' );
4829
				$user->first       = fs_request_get( 'user_first' );
4830
				$user->last        = fs_request_get( 'user_last' );
4831
				$user->is_verified = fs_request_get_bool( 'user_is_verified' );
4832
4833
				$site              = new FS_Site();
4834
				$site->id          = fs_request_get( 'install_id' );
4835
				$site->public_key  = fs_request_get( 'install_public_key' );
4836
				$site->secret_key  = fs_request_get( 'install_secret_key' );
4837
				$site->plan->id    = fs_request_get( 'plan_id' );
4838
				$site->plan->title = fs_request_get( 'plan_title' );
4839
				$site->plan->name  = fs_request_get( 'plan_name' );
4840
4841
				$plans      = array();
4842
				$plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) );
4843
				foreach ( $plans_data as $p ) {
4844
					$plans[] = new FS_Plugin_Plan( $p );
4845
				}
4846
4847
				$this->_set_account( $user, $site, $plans );
4848
4849
				// Reload the page with the keys.
4850
				if ( fs_redirect( $this->_get_admin_page_url() ) ) {
4851
					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...
4852
				}
4853
			}
4854
		}
4855
4856
		/**
4857
		 * @author Vova Feldman (@svovaf)
4858
		 * @since  1.0.7
4859
		 *
4860
		 * @param string $email
4861
		 *
4862
		 * @return FS_User|bool
4863
		 */
4864
		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...
4865
			self::$_static_logger->entrance();
4866
4867
			$email = trim( strtolower( $email ) );
4868
			$users = self::get_all_users();
4869
			if ( is_array( $users ) ) {
4870
				foreach ( $users as $u ) {
4871
					if ( $email === trim( strtolower( $u->email ) ) ) {
4872
						return $u;
4873
					}
4874
				}
4875
			}
4876
4877
			return false;
4878
		}
4879
4880
		#region Account (Loading, Updates & Activation) ------------------------------------------------------------------
4881
4882
		/***
4883
		 * Load account information (user + site).
4884
		 *
4885
		 * @author Vova Feldman (@svovaf)
4886
		 * @since  1.0.1
4887
		 */
4888
		private function _load_account() {
4889
			$this->_logger->entrance();
4890
4891
			$this->do_action( 'before_account_load' );
4892
4893
			$sites    = self::get_all_sites();
4894
			$users    = self::get_all_users();
4895
			$plans    = self::get_all_plans();
4896
			$licenses = self::get_all_licenses();
4897
4898
			if ( $this->_logger->is_on() && is_admin() ) {
4899
				$this->_logger->log( 'sites = ' . var_export( $sites, true ) );
4900
				$this->_logger->log( 'users = ' . var_export( $users, true ) );
4901
				$this->_logger->log( 'plans = ' . var_export( $plans, true ) );
4902
				$this->_logger->log( 'licenses = ' . var_export( $licenses, true ) );
4903
			}
4904
4905
			$site = isset( $sites[ $this->_slug ] ) ? $sites[ $this->_slug ] : false;
4906
4907
			if ( is_object( $site ) &&
4908
			     is_numeric( $site->id ) &&
4909
			     is_numeric( $site->user_id ) &&
4910
			     is_object( $site->plan )
4911
			) {
4912
				// Load site.
4913
				$this->_site       = clone $site;
4914
				$this->_site->plan = $this->_decrypt_entity( $this->_site->plan );
4915
4916
				// Load relevant user.
4917
				$this->_user = clone $users[ $this->_site->user_id ];
4918
4919
				// Load plans.
4920
				$this->_plans = $plans[ $this->_slug ];
4921
				if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) {
4922
					$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...
4923
				} else {
4924
					for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
4925
						if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) {
4926
							$this->_plans[ $i ] = $this->_decrypt_entity( $this->_plans[ $i ] );
4927
						} else {
4928
							unset( $this->_plans[ $i ] );
4929
						}
4930
					}
4931
				}
4932
4933
				// Load licenses.
4934
				$this->_licenses = array();
4935
				if ( is_array( $licenses ) &&
4936
				     isset( $licenses[ $this->_slug ] ) &&
4937
				     isset( $licenses[ $this->_slug ][ $this->_user->id ] )
4938
				) {
4939
					$this->_licenses = $licenses[ $this->_slug ][ $this->_user->id ];
4940
				}
4941
4942
				$this->_license = $this->_get_license_by_id( $this->_site->license_id );
4943
4944
				if ( $this->_site->version != $this->get_plugin_version() ) {
4945
					// If stored install version is different than current installed plugin version,
4946
					// then update plugin version event.
4947
					$this->update_plugin_version_event();
4948
				}
4949
			}
4950
4951
			$this->_register_account_hooks();
4952
		}
4953
4954
		/**
4955
		 * @author Vova Feldman (@svovaf)
4956
		 * @since  1.0.1
4957
		 *
4958
		 * @param FS_User    $user
4959
		 * @param FS_Site    $site
4960
		 * @param bool|array $plans
4961
		 */
4962
		private function _set_account( FS_User $user, FS_Site $site, $plans = false ) {
4963
			$site->slug    = $this->_slug;
4964
			$site->user_id = $user->id;
4965
4966
			$this->_site = $site;
4967
			$this->_user = $user;
4968
			if ( false !== $plans ) {
4969
				$this->_plans = $plans;
4970
			}
4971
4972
			$this->send_install_update();
4973
4974
			$this->_store_account();
4975
4976
		}
4977
4978
		/**
4979
		 * Set user and site identities.
4980
		 *
4981
		 * @author Vova Feldman (@svovaf)
4982
		 * @since  1.0.9
4983
		 *
4984
		 * @param FS_User $user
4985
		 * @param FS_Site $site
4986
		 * @param bool    $redirect
4987
		 *
4988
		 * @return bool False if account already set.
4989
		 */
4990
		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...
4991
			$this->_user = $user;
4992
			$this->_site = $site;
4993
4994
			$this->_sync_plans();
4995
4996
			$this->_enrich_site_plan( false );
4997
4998
			$this->_set_account( $user, $site );
4999
5000
			if ( $this->is_trial() ) {
5001
				// Store trial plan information.
5002
				$this->_enrich_site_trial_plan( true );
5003
			}
5004
5005
			$this->do_action( 'after_account_connection', $user, $site );
5006
5007
			if ( is_numeric( $site->license_id ) ) {
5008
				$this->_license = $this->_get_license_by_id( $site->license_id );
5009
			}
5010
5011
			if ( $this->is_pending_activation() ) {
5012
				// Remove pending activation sticky notice (if still exist).
5013
				$this->_admin_notices->remove_sticky( 'activation_pending' );
5014
5015
				// Remove plugin from pending activation mode.
5016
				unset( $this->_storage->is_pending_activation );
5017
5018
				if ( ! $this->is_paying() ) {
5019
					$this->_admin_notices->add_sticky(
5020
						sprintf( __fs( 'plugin-x-activation-message', $this->_slug ), '<b>' . $this->get_plugin_name() . '</b>' ),
5021
						'activation_complete'
5022
					);
5023
				}
5024
			}
5025
5026
			if ( $this->is_paying() && ! $this->is_premium() ) {
5027
				$this->_admin_notices->add_sticky(
5028
					sprintf(
5029
						__fs( 'activation-with-plan-x-message', $this->_slug ),
5030
						$this->_site->plan->title
5031
					) . ' ' . $this->_get_latest_download_link( sprintf(
5032
						__fs( 'download-latest-x-version', $this->_slug ),
5033
						$this->_site->plan->title
5034
					) ),
5035
					'plan_upgraded',
5036
					__fs( 'yee-haw', $this->_slug ) . '!'
5037
				);
5038
			}
5039
5040
			$plugin_id = fs_request_get( 'plugin_id', false );
5041
5042
			// Store activation time ONLY for plugins (not add-ons).
5043
			if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) {
5044
				$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...
5045
			}
5046
5047
			if ( is_numeric( $plugin_id ) ) {
5048
				if ( $plugin_id != $this->_plugin->id ) {
5049
					// Add-on was installed - sync license right after install.
5050
					if ( $redirect && fs_redirect( fs_nonce_url( $this->_get_admin_page_url(
5051
							'account',
5052
							array(
5053
								'fs_action' => $this->_slug . '_sync_license',
5054
								'plugin_id' => $plugin_id
5055
							)
5056
						), $this->_slug . '_sync_license' ) )
5057
					) {
5058
						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...
5059
					}
5060
5061
				}
5062
			} else {
5063
				// Reload the page with the keys.
5064
				if ( $redirect && fs_redirect( $this->get_after_activation_url( 'after_connect_url' ) ) ) {
5065
					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...
5066
				}
5067
			}
5068
		}
5069
5070
		/**
5071
		 * Install plugin with new user information after approval.
5072
		 *
5073
		 * @author Vova Feldman (@svovaf)
5074
		 * @since  1.0.7
5075
		 */
5076
		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...
5077
			$this->_logger->entrance();
5078
5079
			if ( $this->is_registered() ) {
5080
				return;
5081
			}
5082
5083
			if ( fs_request_is_action( $this->_slug . '_activate_new' ) ) {
5084
//				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...
5085
5086
				$this->_admin_notices->remove_sticky( 'connect_account' );
5087
5088
				if ( fs_request_has( 'user_secret_key' ) ) {
5089
					$user             = new FS_User();
5090
					$user->id         = fs_request_get( 'user_id' );
5091
					$user->public_key = fs_request_get( 'user_public_key' );
5092
					$user->secret_key = fs_request_get( 'user_secret_key' );
5093
5094
					$this->_user = $user;
5095
					$user_result = $this->get_api_user_scope()->get();
5096
					$user        = new FS_User( $user_result );
5097
					$this->_user = $user;
5098
5099
					$site             = new FS_Site();
5100
					$site->id         = fs_request_get( 'install_id' );
5101
					$site->public_key = fs_request_get( 'install_public_key' );
5102
					$site->secret_key = fs_request_get( 'install_secret_key' );
5103
5104
					$this->_site = $site;
5105
					$site_result = $this->get_api_site_scope()->get();
5106
					$site        = new FS_Site( $site_result );
5107
					$this->_site = $site;
5108
5109
					$this->setup_account( $this->_user, $this->_site );
5110
				} else if ( fs_request_has( 'pending_activation' ) ) {
5111
					// Install must be activated via email since
5112
					// user with the same email already exist.
5113
					$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...
5114
					$this->_add_pending_activation_notice( fs_request_get( 'user_email' ) );
5115
5116
					// Reload the page with with pending activation message.
5117
					if ( fs_redirect( $this->get_after_activation_url( 'after_pending_connect_url' ) ) ) {
5118
						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...
5119
					}
5120
				}
5121
			}
5122
		}
5123
5124
		/**
5125
		 * Install plugin with current logged WP user info.
5126
		 *
5127
		 * @author Vova Feldman (@svovaf)
5128
		 * @since  1.0.7
5129
		 */
5130
		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...
5131
			$this->_logger->entrance();
5132
5133
			if ( $this->is_registered() ) {
5134
				return;
5135
			}
5136
5137
			if ( fs_request_is_action( $this->_slug . '_activate_existing' ) && fs_request_is_post() ) {
5138
//				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...
5139
5140
				$this->_admin_notices->remove_sticky( 'connect_account' );
5141
5142
				// Get current logged WP user.
5143
				$current_user = wp_get_current_user();
5144
5145
				// Find the relevant FS user by the email.
5146
				$user = self::_get_user_by_email( $current_user->user_email );
5147
5148
				// We have to set the user before getting user scope API handler.
5149
				$this->_user = $user;
5150
5151
				// Install the plugin.
5152
				$install = $this->get_api_user_scope()->call(
5153
					"/plugins/{$this->get_id()}/installs.json",
5154
					'post',
5155
					$this->get_install_data_for_api( array(
5156
						'uid' => $this->get_anonymous_id(),
5157
					) )
5158
				);
5159
5160
				if ( isset( $install->error ) ) {
5161
					$this->_admin_notices->add(
5162
						sprintf( __fs( 'could-not-activate-x', $this->_slug ), $this->get_plugin_name() ) . ' ' .
5163
						__fs( 'contact-us-with-error-message', $this->_slug ) . ' ' . '<b>' . $install->error->message . '</b>',
5164
						__fs( 'oops', $this->_slug ) . '...',
5165
						'error'
5166
					);
5167
5168
					return;
5169
				}
5170
5171
				$site        = new FS_Site( $install );
5172
				$this->_site = $site;
5173
//				$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...
5174
5175
//				$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...
5176
//				$this->_sync_plans();
5177
5178
				$this->setup_account( $this->_user, $this->_site );
5179
			}
5180
		}
5181
5182
		/**
5183
		 * Tries to activate add-on account based on parent plugin info.
5184
		 *
5185
		 * @author Vova Feldman (@svovaf)
5186
		 * @since  1.0.6
5187
		 *
5188
		 * @param Freemius $parent_fs
5189
		 */
5190
		private function _activate_addon_account( Freemius $parent_fs ) {
5191
			if ( $this->is_registered() ) {
5192
				// Already activated.
5193
				return;
5194
			}
5195
5196
			// Activate add-on with parent plugin credentials.
5197
			$addon_install = $parent_fs->get_api_site_scope()->call(
5198
				"/addons/{$this->_plugin->id}/installs.json",
5199
				'post',
5200
				$this->get_install_data_for_api( array(
5201
					'uid' => $this->get_anonymous_id(),
5202
				) )
5203
			);
5204
5205
			if ( isset( $addon_install->error ) ) {
5206
				$this->_admin_notices->add(
5207
					sprintf( __fs( 'could-not-activate-x', $this->_slug ), $this->get_plugin_name() ) . ' ' .
5208
					__fs( 'contact-us-with-error-message', $this->_slug ) . ' ' . '<b>' . $addon_install->error->message . '</b>',
5209
					__fs( 'oops', $this->_slug ) . '...',
5210
					'error'
5211
				);
5212
5213
				return;
5214
			}
5215
5216
			// First of all, set site info - otherwise we won't
5217
			// be able to invoke API calls.
5218
			$this->_site = new FS_Site( $addon_install );
5219
5220
			// Sync add-on plans.
5221
			$this->_sync_plans();
5222
5223
			// Get site's current plan.
5224
			$this->_site->plan = $this->_get_plan_by_id( $this->_site->plan->id );
5225
5226
			// Get user information based on parent's plugin.
5227
			$user = $parent_fs->get_user();
5228
5229
			$this->_set_account( $user, $this->_site );
5230
5231
			// Sync licenses.
5232
			$this->_sync_licenses();
5233
5234
			// Try to activate premium license.
5235
			$this->_activate_license( true );
5236
		}
5237
5238
		#endregion ------------------------------------------------------------------
5239
5240
		#region Admin Menu Items ------------------------------------------------------------------
5241
5242
		private $_menu_items = array();
5243
5244
		/**
5245
		 * @author Vova Feldman (@svovaf)
5246
		 * @since  1.0.7
5247
		 *
5248
		 * @return string
5249
		 */
5250
		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...
5251
			return $this->_menu->get_slug();
5252
		}
5253
5254
		/**
5255
		 * @author Vova Feldman (@svovaf)
5256
		 * @since  1.0.9
5257
		 */
5258
		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...
5259
			if ( ! $this->is_on() ) {
5260
				return;
5261
			}
5262
5263
			if ( ! $this->has_api_connectivity() && ! $this->enable_anonymous() ) {
5264
				$this->_menu->remove_menu_item();
5265
			} else {
5266
				$this->add_submenu_items();
5267
				$this->add_menu_action();
5268
			}
5269
		}
5270
5271
		/**
5272
		 * Admin dashboard menu items modifications.
5273
		 *
5274
		 * NOTE: admin_menu action executed before admin_init.
5275
		 *
5276
		 * @author Vova Feldman (@svovaf)
5277
		 * @since  1.0.7
5278
		 *
5279
		 */
5280
		private function add_menu_action() {
5281
			if ( $this->is_activation_mode() ) {
5282
				$this->override_plugin_menu_with_activation();
5283
			} else {
5284
				// If not registered try to install user.
5285
				if ( ! $this->is_registered() &&
5286
				     fs_request_is_action( $this->_slug . '_activate_new' )
5287
				) {
5288
					$this->_install_with_new_user();
5289
				}
5290
			}
5291
		}
5292
5293
		/**
5294
		 * @author Vova Feldman (@svovaf)
5295
		 * @since  1.0.1
5296
		 *
5297
		 * @return string
5298
		 */
5299
		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...
5300
			$this->_logger->entrance();
5301
5302
			$page = strtolower( isset( $_REQUEST['page'] ) ? $_REQUEST['page'] : '' );
5303
5304
			$this->_logger->log( 'page = ' . $page );
5305
5306
			foreach ( $this->_menu_items as $priority => $items ) {
5307
				foreach ( $items as $item ) {
5308
					if ( isset( $item['url'] ) ) {
5309
						if ( $page === $item['menu_slug'] ) {
5310
							$this->_logger->log( 'Redirecting to ' . $item['url'] );
5311
5312
							fs_redirect( $item['url'] );
5313
						}
5314
					}
5315
				}
5316
			}
5317
		}
5318
5319
		/**
5320
		 * Remove plugin's all admin menu items & pages, and replace with activation page.
5321
		 *
5322
		 * @author Vova Feldman (@svovaf)
5323
		 * @since  1.0.1
5324
		 */
5325
		private function override_plugin_menu_with_activation() {
5326
			$this->_logger->entrance();
5327
5328
			$hook = false;
5329
5330
			if ( $this->_menu->is_top_level() ) {
5331
				$hook = $this->_menu->override_menu_item( array( &$this, '_connect_page_render' ) );
5332
5333
				if ( false === $hook ) {
5334
					// Create new menu item just for the opt-in.
5335
					$hook = add_menu_page(
5336
						$this->get_plugin_name(),
5337
						$this->get_plugin_name(),
5338
						'manage_options',
5339
						$this->_menu->get_slug(),
5340
						array( &$this, '_connect_page_render' )
5341
					);
5342
				}
5343
			} else {
5344
				$menus = array( $this->_menu->get_parent_slug() );
5345
5346
				if ( $this->_menu->is_override_exact() ) {
5347
					// Make sure the current page is matching the activation page.
5348
					$activation_url = strtolower( $this->get_activation_url() );
5349
					$request_url    = strtolower( $_SERVER['REQUEST_URI'] );
5350
5351
					if ( parse_url( $activation_url, PHP_URL_PATH ) !== parse_url( $request_url, PHP_URL_PATH ) ) {
5352
						// Different path - DO NOT OVERRIDE PAGE.
5353
						return;
5354
					}
5355
5356
					$activation_url_params = array();
5357
					parse_str( parse_url( $activation_url, PHP_URL_QUERY ), $activation_url_params );
5358
5359
					$request_url_params = array();
5360
					parse_str( parse_url( $request_url, PHP_URL_QUERY ), $request_url_params );
5361
5362
5363
					foreach ( $activation_url_params as $key => $val ) {
0 ignored issues
show
Bug introduced by
The expression $activation_url_params of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
5364
						if ( ! isset( $request_url_params[ $key ] ) || $val != $request_url_params[ $key ] ) {
5365
							// Not matching query string - DO NOT OVERRIDE PAGE.
5366
							return;
5367
						}
5368
					}
5369
				}
5370
5371
				foreach ( $menus as $parent_slug ) {
5372
					$hook = $this->_menu->override_submenu_action(
5373
						$parent_slug,
5374
						$this->_menu->get_raw_slug(),
5375
						array( &$this, '_connect_page_render' )
5376
					);
5377
5378
					if ( false !== $hook ) {
5379
						// Found plugin's submenu item.
5380
						break;
5381
					}
5382
				}
5383
			}
5384
5385
			if ( $this->_menu->is_activation_page() ) {
5386
				// Clean admin page from distracting content.
5387
				self::_clean_admin_content_section();
5388
			}
5389
5390
			if ( false !== $hook ) {
5391
				if ( fs_request_is_action( $this->_slug . '_activate_existing' ) ) {
5392
					add_action( "load-$hook", array( &$this, '_install_with_current_user' ) );
5393
				} else if ( fs_request_is_action( $this->_slug . '_activate_new' ) ) {
5394
					add_action( "load-$hook", array( &$this, '_install_with_new_user' ) );
5395
				}
5396
			}
5397
		}
5398
5399
		/**
5400
		 * @author Vova Feldman (@svovaf)
5401
		 * @since  1.0.0
5402
		 *
5403
		 * @return string
5404
		 */
5405
		private function get_top_level_menu_slug() {
5406
			return ( $this->is_addon() ?
5407
				$this->get_parent_instance()->_menu->get_top_level_menu_slug() :
5408
				$this->_menu->get_top_level_menu_slug() );
5409
		}
5410
5411
		/**
5412
		 * Add default Freemius menu items.
5413
		 *
5414
		 * @author Vova Feldman (@svovaf)
5415
		 * @since  1.0.0
5416
		 */
5417
		private function add_submenu_items() {
5418
			$this->_logger->entrance();
5419
5420
			$this->do_action( 'before_admin_menu_init' );
5421
5422
			if ( ! $this->is_addon() ) {
5423
				if ( $this->is_registered() || $this->is_anonymous() ) {
5424
					if ( $this->is_registered() ) {
5425
						// Add user account page.
5426
						$this->add_submenu_item(
5427
							__fs( 'account', $this->_slug ),
5428
							array( &$this, '_account_page_render' ),
5429
							$this->get_plugin_name() . ' &ndash; ' . __fs( 'account', $this->_slug ),
5430
							'manage_options',
5431
							'account',
5432
							array( &$this, '_account_page_load' ),
5433
							WP_FS__DEFAULT_PRIORITY,
5434
							$this->_menu->is_submenu_item_visible( 'account' )
5435
						);
5436
					}
5437
5438
					// Add contact page.
5439
					$this->add_submenu_item(
5440
						__fs( 'contact-us', $this->_slug ),
5441
						array( &$this, '_contact_page_render' ),
5442
						$this->get_plugin_name() . ' &ndash; ' . __fs( 'contact-us', $this->_slug ),
5443
						'manage_options',
5444
						'contact',
5445
						'Freemius::_clean_admin_content_section',
5446
						WP_FS__DEFAULT_PRIORITY,
5447
						$this->_menu->is_submenu_item_visible( 'contact' )
5448
					);
5449
5450
					if ( $this->has_addons() ) {
5451
						$this->add_submenu_item(
5452
							__fs( 'add-ons', $this->_slug ),
5453
							array( &$this, '_addons_page_render' ),
5454
							$this->get_plugin_name() . ' &ndash; ' . __fs( 'add-ons', $this->_slug ),
5455
							'manage_options',
5456
							'addons',
5457
							array( &$this, '_addons_page_load' ),
5458
							WP_FS__LOWEST_PRIORITY - 1,
5459
							$this->_menu->is_submenu_item_visible( 'addons' )
5460
						);
5461
					}
5462
5463
					$show_pricing = ( $this->has_paid_plan() && $this->_menu->is_submenu_item_visible( 'pricing' ) );
5464
					// If user don't have paid plans, add pricing page
5465
					// to support add-ons checkout but don't add the submenu item.
5466
					// || (isset( $_GET['page'] ) && $this->_menu->get_slug( 'pricing' ) == $_GET['page']);
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
5467
5468
					// Add upgrade/pricing page.
5469
					$this->add_submenu_item(
5470
						( $this->is_paying() ? __fs( 'pricing', $this->_slug ) : __fs( 'upgrade', $this->_slug ) . '&nbsp;&nbsp;&#x27a4;' ),
5471
						array( &$this, '_pricing_page_render' ),
5472
						$this->get_plugin_name() . ' &ndash; ' . __fs( 'pricing', $this->_slug ),
5473
						'manage_options',
5474
						'pricing',
5475
						'Freemius::_clean_admin_content_section',
5476
						WP_FS__LOWEST_PRIORITY,
5477
						$show_pricing
5478
					);
5479
				}
5480
			}
5481
5482
5483
			if ( 0 < count( $this->_menu_items ) ) {
5484
				if ( ! $this->_menu->is_top_level() ) {
5485
					fs_enqueue_local_style( 'fs_common', '/admin/common.css' );
5486
5487
					// Append submenu items right after the plugin's submenu item.
5488
					$this->order_sub_submenu_items();
5489
				} else {
5490
					// Append submenu items.
5491
					$this->embed_submenu_items();
5492
				}
5493
			}
5494
		}
5495
5496
		/**
5497
		 * Moved the actual submenu item additions to a separated function,
5498
		 * in order to support sub-submenu items when the plugin's settings
5499
		 * only have a submenu and not top-level menu item.
5500
		 *
5501
		 * @author Vova Feldman (@svovaf)
5502
		 * @since  1.1.4
5503
		 */
5504
		private function embed_submenu_items() {
5505
			$item_template = $this->_menu->is_top_level() ?
5506
				'<span class="fs-submenu-item">%s</span>' :
5507
				'<span class="fs-submenu-item fs-sub">%s</span>';
5508
5509
			ksort( $this->_menu_items );
5510
5511
			foreach ( $this->_menu_items as $priority => $items ) {
5512
				foreach ( $items as $item ) {
5513
					if ( ! isset( $item['url'] ) ) {
5514
						$hook = add_submenu_page(
5515
							$item['show_submenu'] ?
5516
								$this->get_top_level_menu_slug() :
5517
								null,
5518
							$item['page_title'],
5519
							sprintf( $item_template, $item['menu_title'] ),
5520
							$item['capability'],
5521
							$item['menu_slug'],
5522
							$item['render_function']
5523
						);
5524
5525
						if ( false !== $item['before_render_function'] ) {
5526
							add_action( "load-$hook", $item['before_render_function'] );
5527
						}
5528
					} else {
5529
						add_submenu_page(
5530
							$this->get_top_level_menu_slug(),
5531
							$item['page_title'],
5532
							sprintf( $item_template, $item['menu_title'] ),
5533
							$item['capability'],
5534
							$item['menu_slug'],
5535
							array( $this, '' )
5536
						);
5537
					}
5538
				}
5539
			}
5540
		}
5541
5542
		/**
5543
		 * Re-order the submenu items so all Freemius added new submenu items
5544
		 * are added right after the plugin's settings submenu item.
5545
		 *
5546
		 * @author Vova Feldman (@svovaf)
5547
		 * @since  1.1.4
5548
		 */
5549
		private function order_sub_submenu_items() {
5550
			global $submenu;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
5551
5552
			$top_level_menu = &$submenu[ $this->_menu->get_top_level_menu_slug() ];
5553
5554
			$all_submenu_items_after = array();
5555
5556
			$found_submenu_item = false;
5557
5558
			foreach ( $top_level_menu as $submenu_id => $meta ) {
5559
				if ( $found_submenu_item ) {
5560
					// Remove all submenu items after the plugin's submenu item.
5561
					$all_submenu_items_after[] = $meta;
5562
					unset( $top_level_menu[ $submenu_id ] );
5563
				}
5564
5565
				if ( $this->_menu->get_raw_slug() === $meta[2] ) {
5566
					// Found the submenu item, put all below.
5567
					$found_submenu_item = true;
5568
					continue;
5569
				}
5570
			}
5571
5572
			// Embed all plugin's new submenu items.
5573
			$this->embed_submenu_items();
5574
5575
			// Start with specially high number to make sure it's appended.
5576
			$i = max( 10000, max( array_keys( $top_level_menu ) ) + 1 );
5577
			foreach ( $all_submenu_items_after as $meta ) {
5578
				$top_level_menu[ $i ] = $meta;
5579
				$i ++;
5580
			}
5581
5582
			// Sort submenu items.
5583
			ksort( $top_level_menu );
5584
		}
5585
5586
		/**
5587
		 * Displays the Support Forum link when enabled.
5588
		 *
5589
		 * Can be filtered like so:
5590
		 *
5591
		 *  function _fs_show_support_menu( $is_visible, $menu_id ) {
5592
		 *      if ( 'support' === $menu_id ) {
5593
		 *            return _fs->is_registered();
5594
		 *        }
5595
		 *        return $is_visible;
5596
		 *    }
5597
		 *    _fs()->add_filter('is_submenu_visible', '_fs_show_support_menu', 10, 2);
5598
		 *
5599
		 */
5600
		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...
5601
			if ( ! $this->is_on() ) {
5602
				return;
5603
			}
5604
5605
			if ( $this->is_registered() || $this->is_anonymous() ) {
5606
				if ( $this->_menu->is_submenu_item_visible( 'support' ) ) {
5607
					$this->add_submenu_link_item(
5608
						$this->apply_filters( 'support_forum_submenu', __fs( 'support-forum', $this->_slug ) ),
5609
						$this->apply_filters( 'support_forum_url', 'https://wordpress.org/support/plugin/' . $this->_slug ),
5610
						'wp-support-forum',
5611
						'read',
5612
						50
5613
					);
5614
				}
5615
			}
5616
		}
5617
5618
		/**
5619
		 * @author Vova Feldman (@svovaf)
5620
		 * @since  1.0.1
5621
		 *
5622
		 * @param string        $menu_title
5623
		 * @param callable      $render_function
5624
		 * @param bool|string   $page_title
5625
		 * @param string        $capability
5626
		 * @param bool|string   $menu_slug
5627
		 * @param bool|callable $before_render_function
5628
		 * @param int           $priority
5629
		 * @param bool          $show_submenu
5630
		 */
5631
		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...
5632
			$menu_title,
5633
			$render_function,
5634
			$page_title = false,
5635
			$capability = 'manage_options',
5636
			$menu_slug = false,
5637
			$before_render_function = false,
5638
			$priority = WP_FS__DEFAULT_PRIORITY,
5639
			$show_submenu = true
5640
		) {
5641
			$this->_logger->entrance( 'Title = ' . $menu_title );
5642
5643
			if ( $this->is_addon() ) {
5644
				$parent_fs = $this->get_parent_instance();
5645
5646
				if ( is_object( $parent_fs ) ) {
5647
					$parent_fs->add_submenu_item(
5648
						$menu_title,
5649
						$render_function,
5650
						$page_title,
5651
						$capability,
5652
						$menu_slug,
5653
						$before_render_function,
5654
						$priority,
5655
						$show_submenu
5656
					);
5657
5658
					return;
5659
				}
5660
			}
5661
5662
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
5663
				$this->_menu_items[ $priority ] = array();
5664
			}
5665
5666
			$this->_menu_items[ $priority ][] = array(
5667
				'page_title'             => is_string( $page_title ) ? $page_title : $menu_title,
5668
				'menu_title'             => $menu_title,
5669
				'capability'             => $capability,
5670
				'menu_slug'              => $this->_menu->get_slug( is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ) ),
5671
				'render_function'        => $render_function,
5672
				'before_render_function' => $before_render_function,
5673
				'show_submenu'           => $show_submenu,
5674
			);
5675
		}
5676
5677
		/**
5678
		 * @author Vova Feldman (@svovaf)
5679
		 * @since  1.0.1
5680
		 *
5681
		 * @param string $menu_title
5682
		 * @param string $url
5683
		 * @param bool   $menu_slug
5684
		 * @param string $capability
5685
		 * @param int    $priority
5686
		 *
5687
		 */
5688
		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...
5689
			$menu_title,
5690
			$url,
5691
			$menu_slug = false,
5692
			$capability = 'read',
5693
			$priority = WP_FS__DEFAULT_PRIORITY
5694
		) {
5695
			$this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url );
5696
5697
			if ( $this->is_addon() ) {
5698
				$parent_fs = $this->get_parent_instance();
5699
5700
				if ( is_object( $parent_fs ) ) {
5701
					$parent_fs->add_submenu_link_item(
5702
						$menu_title,
5703
						$url,
5704
						$menu_slug,
5705
						$capability,
5706
						$priority
5707
					);
5708
5709
					return;
5710
				}
5711
			}
5712
5713
			if ( ! isset( $this->_menu_items[ $priority ] ) ) {
5714
				$this->_menu_items[ $priority ] = array();
5715
			}
5716
5717
			$this->_menu_items[ $priority ][] = array(
5718
				'menu_title'             => $menu_title,
5719
				'capability'             => $capability,
5720
				'menu_slug'              => $this->_menu->get_slug( is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ) ),
5721
				'url'                    => $url,
5722
				'page_title'             => $menu_title,
5723
				'render_function'        => 'fs_dummy',
5724
				'before_render_function' => '',
5725
			);
5726
		}
5727
5728
		#endregion ------------------------------------------------------------------
5729
5730
		/* Actions / Hooks / Filters
5731
		------------------------------------------------------------------------------------------------------------------*/
5732
		/**
5733
		 * @author Vova Feldman (@svovaf)
5734
		 * @since  1.1.7
5735
		 *
5736
		 * @param string $tag
5737
		 *
5738
		 * @return string
5739
		 */
5740
		private function get_action_tag( $tag ) {
5741
			return 'fs_' . $tag . '_' . $this->_slug;
5742
		}
5743
5744
		/**
5745
		 * Do action, specific for the current context plugin.
5746
		 *
5747
		 * @author Vova Feldman (@svovaf)
5748
		 * @since  1.0.1
5749
		 *
5750
		 * @param string $tag     The name of the action to be executed.
5751
		 * @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...
5752
		 *                        functions hooked to the action. Default empty.
5753
		 *
5754
		 * @uses   do_action()
5755
		 */
5756
		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...
5757
			$this->_logger->entrance( $tag );
5758
5759
			$args = func_get_args();
5760
5761
			call_user_func_array( 'do_action', array_merge(
5762
					array( $this->get_action_tag( $tag ) ),
5763
					array_slice( $args, 1 ) )
5764
			);
5765
		}
5766
5767
		/**
5768
		 * Add action, specific for the current context plugin.
5769
		 *
5770
		 * @author Vova Feldman (@svovaf)
5771
		 * @since  1.0.1
5772
		 *
5773
		 * @param string   $tag
5774
		 * @param callable $function_to_add
5775
		 * @param int      $priority
5776
		 * @param int      $accepted_args
5777
		 *
5778
		 * @uses   add_action()
5779
		 */
5780
		function add_action( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $accepted_args = 1 ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5781
			$this->_logger->entrance( $tag );
5782
5783
			add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
5784
		}
5785
5786
		/**
5787
		 * Apply filter, specific for the current context plugin.
5788
		 *
5789
		 * @author Vova Feldman (@svovaf)
5790
		 * @since  1.0.9
5791
		 *
5792
		 * @param string $tag   The name of the filter hook.
5793
		 * @param mixed  $value The value on which the filters hooked to `$tag` are applied on.
5794
		 *
5795
		 * @return mixed The filtered value after all hooked functions are applied to it.
5796
		 *
5797
		 * @uses   apply_filters()
5798
		 */
5799
		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...
5800
			$this->_logger->entrance( $tag );
5801
5802
			$args = func_get_args();
5803
			array_unshift( $args, $this->_slug );
5804
5805
			return call_user_func_array( 'fs_apply_filter', $args );
5806
		}
5807
5808
		/**
5809
		 * Add filter, specific for the current context plugin.
5810
		 *
5811
		 * @author Vova Feldman (@svovaf)
5812
		 * @since  1.0.9
5813
		 *
5814
		 * @param string   $tag
5815
		 * @param callable $function_to_add
5816
		 * @param int      $priority
5817
		 * @param int      $accepted_args
5818
		 *
5819
		 * @uses   add_filter()
5820
		 */
5821
		function add_filter( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $accepted_args = 1 ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5822
			$this->_logger->entrance( $tag );
5823
5824
			add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
5825
		}
5826
5827
		/**
5828
		 * Check if has filter.
5829
		 *
5830
		 * @author Vova Feldman (@svovaf)
5831
		 * @since  1.1.4
5832
		 *
5833
		 * @param string        $tag
5834
		 * @param callable|bool $function_to_check Optional. The callback to check for. Default false.
5835
		 *
5836
		 * @return false|int
5837
		 *
5838
		 * @uses   has_filter()
5839
		 */
5840
		function has_filter( $tag, $function_to_check = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5841
			$this->_logger->entrance( $tag );
5842
5843
			return has_filter( $this->get_action_tag( $tag ), $function_to_check );
5844
		}
5845
5846
		/**
5847
		 * Override default i18n text phrases.
5848
		 *
5849
		 * @author Vova Feldman (@svovaf)
5850
		 * @since  1.1.6
5851
		 *
5852
		 * @param string[] string $key_value
5853
		 *
5854
		 * @uses   fs_override_i18n()
5855
		 */
5856
		function override_i18n( $key_value ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
5857
			fs_override_i18n( $key_value, $this->_slug );
5858
		}
5859
5860
		/* Account Page
5861
		------------------------------------------------------------------------------------------------------------------*/
5862
		/**
5863
		 * Update site information.
5864
		 *
5865
		 * @author Vova Feldman (@svovaf)
5866
		 * @since  1.0.1
5867
		 *
5868
		 * @param bool $store Flush to Database if true.
5869
		 */
5870
		private function _store_site( $store = true ) {
5871
			$this->_logger->entrance();
5872
5873
			$encrypted_site       = clone $this->_site;
5874
			$encrypted_site->plan = $this->_encrypt_entity( $this->_site->plan );
5875
5876
			$sites                 = self::get_all_sites();
5877
			$sites[ $this->_slug ] = $encrypted_site;
5878
			self::$_accounts->set_option( 'sites', $sites, $store );
5879
		}
5880
5881
		/**
5882
		 * Update plugin's plans information.
5883
		 *
5884
		 * @author Vova Feldman (@svovaf)
5885
		 * @since  1.0.2
5886
		 *
5887
		 * @param bool $store Flush to Database if true.
5888
		 */
5889
		private function _store_plans( $store = true ) {
5890
			$this->_logger->entrance();
5891
5892
			$plans = self::get_all_plans();
5893
5894
			// Copy plans.
5895
			$encrypted_plans = array();
5896
			for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
5897
				$encrypted_plans[] = $this->_encrypt_entity( $this->_plans[ $i ] );
5898
			}
5899
5900
			$plans[ $this->_slug ] = $encrypted_plans;
5901
			self::$_accounts->set_option( 'plans', $plans, $store );
5902
		}
5903
5904
		/**
5905
		 * Update user's plugin licenses.
5906
		 *
5907
		 * @author Vova Feldman (@svovaf)
5908
		 * @since  1.0.5
5909
		 *
5910
		 * @param bool                $store
5911
		 * @param string|bool         $plugin_slug
5912
		 * @param FS_Plugin_License[] $licenses
5913
		 */
5914
		private function _store_licenses( $store = true, $plugin_slug = false, $licenses = array() ) {
5915
			$this->_logger->entrance();
5916
5917
			$all_licenses = self::get_all_licenses();
5918
5919
			if ( ! is_string( $plugin_slug ) ) {
5920
				$plugin_slug = $this->_slug;
5921
				$licenses    = $this->_licenses;
5922
			}
5923
5924
			if ( ! isset( $all_licenses[ $plugin_slug ] ) ) {
5925
				$all_licenses[ $plugin_slug ] = array();
5926
			}
5927
5928
			$all_licenses[ $plugin_slug ][ $this->_user->id ] = $licenses;
5929
5930
			self::$_accounts->set_option( 'licenses', $all_licenses, $store );
5931
		}
5932
5933
		/**
5934
		 * Update user information.
5935
		 *
5936
		 * @author Vova Feldman (@svovaf)
5937
		 * @since  1.0.1
5938
		 *
5939
		 * @param bool $store Flush to Database if true.
5940
		 */
5941
		private function _store_user( $store = true ) {
5942
			$this->_logger->entrance();
5943
5944
			$users                     = self::get_all_users();
5945
			$users[ $this->_user->id ] = $this->_user;
5946
			self::$_accounts->set_option( 'users', $users, $store );
5947
		}
5948
5949
		/**
5950
		 * Update new updates information.
5951
		 *
5952
		 * @author Vova Feldman (@svovaf)
5953
		 * @since  1.0.4
5954
		 *
5955
		 * @param FS_Plugin_Tag|null $update
5956
		 * @param bool               $store Flush to Database if true.
5957
		 * @param bool|number        $plugin_id
5958
		 */
5959
		private function _store_update( $update, $store = true, $plugin_id = false ) {
5960
			$this->_logger->entrance();
5961
5962
			if ( $update instanceof FS_Plugin_Tag ) {
5963
				$update->updated = time();
5964
			}
5965
5966
			if ( ! is_numeric( $plugin_id ) ) {
5967
				$plugin_id = $this->_plugin->id;
5968
			}
5969
5970
			$updates               = self::get_all_updates();
5971
			$updates[ $plugin_id ] = $update;
5972
			self::$_accounts->set_option( 'updates', $updates, $store );
5973
		}
5974
5975
		/**
5976
		 * Update new updates information.
5977
		 *
5978
		 * @author   Vova Feldman (@svovaf)
5979
		 * @since    1.0.6
5980
		 *
5981
		 * @param FS_Plugin[] $plugin_addons
5982
		 * @param bool        $store Flush to Database if true.
5983
		 */
5984
		private function _store_addons( $plugin_addons, $store = true ) {
5985
			$this->_logger->entrance();
5986
5987
			$addons                       = self::get_all_addons();
5988
			$addons[ $this->_plugin->id ] = $plugin_addons;
5989
			self::$_accounts->set_option( 'addons', $addons, $store );
5990
		}
5991
5992
		/**
5993
		 * Delete plugin's associated add-ons.
5994
		 *
5995
		 * @author   Vova Feldman (@svovaf)
5996
		 * @since    1.0.8
5997
		 *
5998
		 * @param bool $store
5999
		 *
6000
		 * @return bool
6001
		 */
6002
		private function _delete_account_addons( $store = true ) {
6003
			$all_addons = self::get_all_account_addons();
6004
6005
			if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) {
6006
				return false;
6007
			}
6008
6009
			unset( $all_addons[ $this->_plugin->id ] );
6010
6011
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
6012
6013
			return true;
6014
		}
6015
6016
		/**
6017
		 * Update account add-ons list.
6018
		 *
6019
		 * @author   Vova Feldman (@svovaf)
6020
		 * @since    1.0.6
6021
		 *
6022
		 * @param FS_Plugin[] $addons
6023
		 * @param bool        $store Flush to Database if true.
6024
		 */
6025
		private function _store_account_addons( $addons, $store = true ) {
6026
			$this->_logger->entrance();
6027
6028
			$all_addons                       = self::get_all_account_addons();
6029
			$all_addons[ $this->_plugin->id ] = $addons;
6030
			self::$_accounts->set_option( 'account_addons', $all_addons, $store );
6031
		}
6032
6033
		/**
6034
		 * Store account params in the Database.
6035
		 *
6036
		 * @author Vova Feldman (@svovaf)
6037
		 * @since  1.0.1
6038
		 */
6039
		private function _store_account() {
6040
			$this->_logger->entrance();
6041
6042
			$this->_store_site( false );
6043
			$this->_store_user( false );
6044
			$this->_store_plans( false );
6045
			$this->_store_licenses( false );
6046
6047
			self::$_accounts->store();
6048
		}
6049
6050
		/**
6051
		 * Sync user's information.
6052
		 *
6053
		 * @author Vova Feldman (@svovaf)
6054
		 * @since  1.0.3
6055
		 * @uses   FS_Api
6056
		 */
6057
		private function _handle_account_user_sync() {
6058
			$this->_logger->entrance();
6059
6060
			$api = $this->get_api_user_scope();
6061
6062
			// Get user's information.
6063
			$user = $api->get( '/', true );
6064
6065
			if ( isset( $user->id ) ) {
6066
				$this->_user->first = $user->first;
6067
				$this->_user->last  = $user->last;
6068
				$this->_user->email = $user->email;
6069
6070
				$is_menu_item_account_visible = $this->_menu->is_submenu_item_visible( 'account' );
6071
6072
				if ( $user->is_verified &&
6073
				     ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified )
6074
				) {
6075
					$this->_user->is_verified = true;
6076
6077
					$this->do_action( 'account_email_verified', $user->email );
6078
6079
					$this->_admin_notices->add(
6080
						__fs( 'email-verified-message', $this->_slug ),
6081
						__fs( 'right-on', $this->_slug ) . '!',
6082
						'success',
6083
						// Make admin sticky if account menu item is invisible,
6084
						// since the page will be auto redirected to the plugin's
6085
						// main settings page, and the non-sticky message
6086
						// will disappear.
6087
						! $is_menu_item_account_visible,
6088
						false,
6089
						'email_verified'
6090
					);
6091
				}
6092
6093
				// Flush user details to DB.
6094
				$this->_store_user();
6095
6096
				$this->do_action( 'after_account_user_sync', $user );
6097
6098
				/**
6099
				 * If account menu item is hidden, redirect to plugin's main settings page.
6100
				 *
6101
				 * @author Vova Feldman (@svovaf)
6102
				 * @since  1.1.6
6103
				 *
6104
				 * @link   https://github.com/Freemius/wordpress-sdk/issues/6
6105
				 */
6106
				if ( ! $is_menu_item_account_visible ) {
6107
					if ( fs_redirect( $this->_get_admin_page_url() ) ) {
6108
						exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _handle_account_user_sync() 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...
6109
					}
6110
				}
6111
			}
6112
		}
6113
6114
		/**
6115
		 * @author Vova Feldman (@svovaf)
6116
		 * @since  1.0.5
6117
		 * @uses   FS_Api
6118
		 *
6119
		 * @param bool $flush
6120
		 *
6121
		 * @return object|\FS_Site
6122
		 */
6123
		private function _fetch_site( $flush = false ) {
6124
			$this->_logger->entrance();
6125
			$api = $this->get_api_site_scope();
6126
6127
			$site = $api->get( '/', $flush );
6128
6129
			if ( ! isset( $site->error ) ) {
6130
				$site          = new FS_Site( $site );
6131
				$site->slug    = $this->_slug;
6132
				$site->version = $this->get_plugin_version();
6133
			}
6134
6135
			return $site;
6136
		}
6137
6138
		/**
6139
		 * @param bool $store
6140
		 *
6141
		 * @return FS_Plugin_Plan|object|false
6142
		 */
6143
		private function _enrich_site_plan( $store = true ) {
6144
			// Try to load plan from local cache.
6145
			$plan = $this->_get_plan_by_id( $this->_site->plan->id );
6146
6147
			if ( false === $plan ) {
6148
				$plan = $this->_fetch_site_plan();
6149
			}
6150
6151
			if ( $plan instanceof FS_Plugin_Plan ) {
6152
				$this->_update_plan( $plan, $store );
6153
			}
6154
6155
			return $plan;
6156
		}
6157
6158
		/**
6159
		 * @author Vova Feldman (@svovaf)
6160
		 * @since  1.0.9
6161
		 * @uses   FS_Api
6162
		 *
6163
		 * @param bool $store
6164
		 *
6165
		 * @return FS_Plugin_Plan|object|false
6166
		 */
6167
		private function _enrich_site_trial_plan( $store = true ) {
6168
			// Try to load plan from local cache.
6169
			$trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id );
6170
6171
			if ( false === $trial_plan ) {
6172
				$trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id );
6173
			}
6174
6175
			if ( $trial_plan instanceof FS_Plugin_Plan ) {
6176
				$this->_storage->store( 'trial_plan', $trial_plan, $store );
6177
			}
6178
6179
			return $trial_plan;
6180
		}
6181
6182
		/**
6183
		 * @author Vova Feldman (@svovaf)
6184
		 * @since  1.0.9
6185
		 * @uses   FS_Api
6186
		 *
6187
		 * @param number|bool $license_id
6188
		 *
6189
		 * @return FS_Subscription|object|bool
6190
		 */
6191
		private function _fetch_site_license_subscription( $license_id = false ) {
6192
			$this->_logger->entrance();
6193
			$api = $this->get_api_site_scope();
6194
6195
			if ( ! is_numeric( $license_id ) ) {
6196
				$license_id = $this->_license->id;
6197
			}
6198
6199
			$result = $api->get( "/licenses/{$license_id}/subscriptions.json", true );
6200
6201
			return ! isset( $result->error ) ?
6202
				( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ?
6203
					new FS_Subscription( $result->subscriptions[0] ) :
6204
					false
6205
				) :
6206
				$result;
6207
		}
6208
6209
		/**
6210
		 * @author Vova Feldman (@svovaf)
6211
		 * @since  1.0.4
6212
		 * @uses   FS_Api
6213
		 *
6214
		 * @param number|bool $plan_id
6215
		 *
6216
		 * @return FS_Plugin_Plan|object
6217
		 */
6218
		private function _fetch_site_plan( $plan_id = false ) {
6219
			$this->_logger->entrance();
6220
			$api = $this->get_api_site_scope();
6221
6222
			if ( ! is_numeric( $plan_id ) ) {
6223
				$plan_id = $this->_site->plan->id;
6224
			}
6225
6226
			$plan = $api->get( "/plans/{$plan_id}.json", true );
6227
6228
			return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan;
6229
		}
6230
6231
		/**
6232
		 * @author Vova Feldman (@svovaf)
6233
		 * @since  1.0.5
6234
		 * @uses   FS_Api
6235
		 *
6236
		 * @return FS_Plugin_Plan[]|object
6237
		 */
6238
		private function _fetch_plugin_plans() {
6239
			$this->_logger->entrance();
6240
			$api = $this->get_api_site_scope();
6241
6242
			$result = $api->get( '/plans.json', true );
6243
6244
			if ( ! $this->is_api_error( $result ) ) {
6245
				for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) {
6246
					$result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] );
6247
				}
6248
6249
				$result = $result->plans;
6250
			}
6251
6252
			return $result;
6253
		}
6254
6255
		/**
6256
		 * @author Vova Feldman (@svovaf)
6257
		 * @since  1.0.5
6258
		 * @uses   FS_Api
6259
		 *
6260
		 * @param number|bool $plugin_id
6261
		 *
6262
		 * @return FS_Plugin_License[]|object
6263
		 */
6264
		private function _fetch_licenses( $plugin_id = false ) {
6265
			$this->_logger->entrance();
6266
6267
			$api = $this->get_api_user_scope();
6268
6269
			if ( ! is_numeric( $plugin_id ) ) {
6270
				$plugin_id = $this->_plugin->id;
6271
			}
6272
6273
			$result = $api->get( "/plugins/{$plugin_id}/licenses.json", true );
6274
6275
			if ( ! isset( $result->error ) ) {
6276
				for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) {
6277
					$result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] );
6278
				}
6279
6280
				$result = $result->licenses;
6281
			}
6282
6283
			return $result;
6284
		}
6285
6286
		/**
6287
		 * @author Vova Feldman (@svovaf)
6288
		 * @since  1.0.4
6289
		 *
6290
		 * @param FS_Plugin_Plan $plan
6291
		 * @param bool           $store
6292
		 */
6293
		private function _update_plan( $plan, $store = false ) {
6294
			$this->_logger->entrance();
6295
6296
			$this->_site->plan = $plan;
6297
			$this->_store_site( $store );
6298
		}
6299
6300
		/**
6301
		 * @author Vova Feldman (@svovaf)
6302
		 * @since  1.0.5
6303
		 *
6304
		 * @param FS_Plugin_License[] $licenses
6305
		 * @param string|bool         $plugin_slug
6306
		 */
6307
		private function _update_licenses( $licenses, $plugin_slug = false ) {
6308
			$this->_logger->entrance();
6309
6310
			if ( is_array( $licenses ) ) {
6311
				for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) {
6312
					$licenses[ $i ]->updated = time();
6313
				}
6314
			}
6315
6316
			if ( ! is_string( $plugin_slug ) ) {
6317
				$this->_licenses = $licenses;
6318
			}
6319
6320
			$this->_store_licenses( true, $plugin_slug, $licenses );
6321
		}
6322
6323
		/**
6324
		 * @author Vova Feldman (@svovaf)
6325
		 * @since  1.0.4
6326
		 *
6327
		 * @param bool|number $plugin_id
6328
		 * @param bool        $flush Since 1.1.7.3
6329
		 *
6330
		 * @return object|false New plugin tag info if exist.
6331
		 */
6332
		private function _fetch_newer_version( $plugin_id = false, $flush = true ) {
6333
			$latest_tag = $this->_fetch_latest_version( $plugin_id, $flush );
6334
6335
			if ( ! is_object( $latest_tag ) ) {
6336
				return false;
6337
			}
6338
6339
			// Check if version is actually newer.
6340
			$has_new_version =
6341
				// If it's an non-installed add-on then always return latest.
6342
				( $this->_is_addon_id( $plugin_id ) && ! $this->is_addon_activated( $plugin_id ) ) ||
6343
				// Compare versions.
6344
				version_compare( $this->get_plugin_version(), $latest_tag->version, '<' );
6345
6346
			$this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' );
6347
6348
			return $has_new_version ? $latest_tag : false;
6349
		}
6350
6351
		/**
6352
		 * @author Vova Feldman (@svovaf)
6353
		 * @since  1.0.5
6354
		 *
6355
		 * @param bool|number $plugin_id
6356
		 * @param bool        $flush Since 1.1.7.3
6357
		 *
6358
		 * @return bool|FS_Plugin_Tag
6359
		 */
6360
		function get_update( $plugin_id = false, $flush = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6361
			$this->_logger->entrance();
6362
6363
			if ( ! is_numeric( $plugin_id ) ) {
6364
				$plugin_id = $this->_plugin->id;
6365
			}
6366
6367
			$this->_check_updates( true, $plugin_id, $flush );
6368
			$updates = $this->get_all_updates();
6369
6370
			return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
6371
		}
6372
6373
		/**
6374
		 * Check if site assigned with active license.
6375
		 *
6376
		 * @author Vova Feldman (@svovaf)
6377
		 * @since  1.0.6
6378
		 */
6379
		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...
6380
			return (
6381
				is_object( $this->_license ) &&
6382
				is_numeric( $this->_license->id ) &&
6383
				! $this->_license->is_expired()
6384
			);
6385
		}
6386
6387
		/**
6388
		 * Check if site assigned with license with enabled features.
6389
		 *
6390
		 * @author Vova Feldman (@svovaf)
6391
		 * @since  1.0.6
6392
		 *
6393
		 * @return bool
6394
		 */
6395
		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...
6396
			return (
6397
				is_object( $this->_license ) &&
6398
				is_numeric( $this->_license->id ) &&
6399
				$this->_license->is_features_enabled()
6400
			);
6401
		}
6402
6403
		/**
6404
		 * Check if user is a trial or have feature enabled license.
6405
		 *
6406
		 * @author Vova Feldman (@svovaf)
6407
		 * @since  1.1.7
6408
		 *
6409
		 * @return bool
6410
		 */
6411
		function can_use_premium_code() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
6412
			return $this->is_trial() || $this->has_features_enabled_license();
6413
		}
6414
6415
		/**
6416
		 * Sync site's plan.
6417
		 *
6418
		 * @author Vova Feldman (@svovaf)
6419
		 * @since  1.0.3
6420
		 *
6421
		 * @uses   FS_Api
6422
		 *
6423
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
6424
		 *                         the admin.
6425
		 */
6426
		private function _sync_license( $background = false ) {
6427
			$this->_logger->entrance();
6428
6429
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
6430
6431
			$is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() );
6432
6433
			if ( $is_addon_sync ) {
6434
				$this->_sync_addon_license( $plugin_id, $background );
6435
			} else {
6436
				$this->_sync_plugin_license( $background );
6437
			}
6438
6439
			$this->do_action( 'after_account_plan_sync', $this->_site->plan->name );
6440
		}
6441
6442
		/**
6443
		 * Sync plugin's add-on license.
6444
		 *
6445
		 * @author Vova Feldman (@svovaf)
6446
		 * @since  1.0.6
6447
		 * @uses   FS_Api
6448
		 *
6449
		 * @param number $addon_id
6450
		 * @param bool   $background
6451
		 */
6452
		private function _sync_addon_license( $addon_id, $background ) {
6453
			$this->_logger->entrance();
6454
6455
			if ( $this->is_addon_activated( $addon_id ) ) {
6456
				// If already installed, use add-on sync.
6457
				$fs_addon = self::get_instance_by_id( $addon_id );
6458
				$fs_addon->_sync_license( $background );
6459
6460
				return;
6461
			}
6462
6463
			// Validate add-on exists.
6464
			$addon = $this->get_addon( $addon_id );
6465
6466
			if ( ! is_object( $addon ) ) {
6467
				return;
6468
			}
6469
6470
			// Add add-on into account add-ons.
6471
			$account_addons = $this->get_account_addons();
6472
			if ( ! is_array( $account_addons ) ) {
6473
				$account_addons = array();
6474
			}
6475
			$account_addons[] = $addon->id;
6476
			$account_addons   = array_unique( $account_addons );
6477
			$this->_store_account_addons( $account_addons );
6478
6479
			// Load add-on licenses.
6480
			$licenses = $this->_fetch_licenses( $addon->id );
6481
6482
			// Sync add-on licenses.
6483
			if ( ! isset( $licenses->error ) ) {
6484
				$this->_update_licenses( $licenses, $addon->slug );
6485
6486
				if ( ! $this->is_addon_installed( $addon->slug ) && FS_License_Manager::has_premium_license( $licenses ) ) {
6487
					$plans_result = $this->get_api_site_or_plugin_scope()->get( "/addons/{$addon_id}/plans.json" );
6488
6489
					if ( ! isset( $plans_result->error ) ) {
6490
						$plans = array();
6491
						foreach ( $plans_result->plans as $plan ) {
6492
							$plans[] = new FS_Plugin_Plan( $plan );
6493
						}
6494
6495
						$this->_admin_notices->add_sticky(
6496
							FS_Plan_Manager::instance()->has_free_plan( $plans ) ?
6497
								sprintf(
6498
									__fs( 'addon-successfully-upgraded-message', $this->_slug ),
6499
									$addon->title
6500
								) . ' ' . $this->_get_latest_download_link(
6501
									__fs( 'download-latest-version', $this->_slug ),
6502
									$addon_id
6503
								)
6504
								:
6505
								sprintf(
6506
									__fs( 'addon-successfully-purchased-message', $this->_slug ),
6507
									$addon->title
6508
								) . ' ' . $this->_get_latest_download_link(
6509
									__fs( 'download-latest-version', $this->_slug ),
6510
									$addon_id
6511
								),
6512
							'addon_plan_upgraded_' . $addon->slug,
6513
							__fs( 'yee-haw', $this->_slug ) . '!'
6514
						);
6515
					}
6516
				}
6517
			}
6518
		}
6519
6520
		/**
6521
		 * Sync site's plugin plan.
6522
		 *
6523
		 * @author Vova Feldman (@svovaf)
6524
		 * @since  1.0.6
6525
		 * @uses   FS_Api
6526
		 *
6527
		 * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by
6528
		 *                         the admin.
6529
		 */
6530
		private function _sync_plugin_license( $background = false ) {
6531
			$this->_logger->entrance();
6532
6533
			// Sync site info.
6534
			$site = $this->send_install_update( array(), true );
6535
6536
			$plan_change = 'none';
6537
6538
			if ( $this->is_api_error( $site ) ) {
6539
				// Show API messages only if not background sync or if paying customer.
6540
				if ( ! $background || $this->is_paying() ) {
6541
					// Try to ping API to see if not blocked.
6542
					if ( ! FS_Api::test() ) {
6543
						/**
6544
						 * Failed to ping API - blocked!
6545
						 *
6546
						 * @author Vova Feldman (@svovaf)
6547
						 * @since  1.1.6 Only show message related to one of the Freemius powered plugins. Once it will be resolved it will fix the issue for all plugins anyways. There's no point to scare users with multiple error messages.
6548
						 */
6549
						$api = $this->get_api_site_scope();
6550
6551
						if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) {
6552
							self::$_global_admin_notices->add(
6553
								sprintf(
6554
									__fs( 'server-blocking-access', $this->_slug ),
6555
									$this->get_plugin_name(),
6556
									'<a href="' . $api->get_url() . '" target="_blank">' . $api->get_url() . '</a>'
6557
								) . '<br> ' . __fs( 'server-error-message', $this->_slug ) . var_export( $site->error, true ),
6558
								__fs( 'oops', $this->_slug ) . '...',
6559
								'error',
6560
								$background,
6561
								false,
6562
								'api_blocked'
6563
							);
6564
						}
6565
					} else {
6566
						// Authentication params are broken.
6567
						$this->_admin_notices->add(
6568
							__fs( 'wrong-authentication-param-message', $this->_slug ),
6569
							__fs( 'oops', $this->_slug ) . '...',
6570
							'error'
6571
						);
6572
					}
6573
				}
6574
6575
				// No reason to continue with license sync while there are API issues.
6576
				return;
6577
			}
6578
6579
			// Remove sticky API connectivity message.
6580
			self::$_global_admin_notices->remove_sticky( 'api_blocked' );
6581
6582
			$site = new FS_Site( $site );
6583
6584
			// Sync plans.
6585
			$this->_sync_plans();
6586
6587
			if ( ! $this->has_paid_plan() ) {
6588
				$this->_site = $site;
6589
				$this->_enrich_site_plan( true );
6590
				$this->_store_site();
6591
			} else {
6592
				// Sync licenses.
6593
				$this->_sync_licenses();
6594
6595
				// Check if plan / license changed.
6596
				if ( ! FS_Entity::equals( $site->plan, $this->_site->plan ) ||
6597
				     // Check if trial started.
6598
				     $site->trial_plan_id != $this->_site->trial_plan_id ||
6599
				     $site->trial_ends != $this->_site->trial_ends ||
6600
				     // Check if license changed.
6601
				     $site->license_id != $this->_site->license_id
6602
				) {
6603
					if ( $site->is_trial() && ( ! $this->_site->is_trial() || $site->trial_ends != $this->_site->trial_ends ) ) {
6604
						// New trial started.
6605
						$this->_site = $site;
6606
						$plan_change = 'trial_started';
6607
6608
						// Store trial plan information.
6609
						$this->_enrich_site_trial_plan( true );
6610
6611
						// For trial with subscription use-case.
6612
						$new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id );
6613
6614
						if ( is_object( $new_license ) && ! $new_license->is_expired() ) {
6615
							$this->_site = $site;
6616
							$this->_update_site_license( $new_license );
6617
							$this->_store_licenses();
6618
							$this->_enrich_site_plan( true );
6619
6620
							$this->_sync_site_subscription( $this->_license );
6621
						}
6622
					} else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) {
6623
						// Was in trial, but now trial expired and no license ID.
6624
						// New trial started.
6625
						$this->_site = $site;
6626
						$plan_change = 'trial_expired';
6627
6628
						// Clear trial plan information.
6629
						$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...
6630
6631
					} else {
6632
						$is_free = $this->is_free_plan();
6633
6634
						// Make sure license exist and not expired.
6635
						$new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id );
6636
6637
						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...
6638
							// The license is expired, so ignore upgrade method.
6639
						} else {
6640
							// License changed.
6641
							$this->_site = $site;
6642
							$this->_update_site_license( $new_license );
6643
							$this->_store_licenses();
6644
							$this->_enrich_site_plan( true );
6645
6646
							$plan_change = $is_free ?
6647
								'upgraded' :
6648
								( is_object( $new_license ) ?
6649
									'changed' :
6650
									'downgraded' );
6651
						}
6652
					}
6653
6654
					// Store updated site info.
6655
					$this->_store_site();
6656
				} else {
6657
					if ( is_object( $this->_license ) && $this->_license->is_expired() ) {
6658
						if ( ! $this->has_features_enabled_license() ) {
6659
							$this->_deactivate_license();
6660
							$plan_change = 'downgraded';
6661
						} else {
6662
							$plan_change = 'expired';
6663
						}
6664
					}
6665
6666
					if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) {
6667
						$this->_sync_site_subscription( $this->_license );
6668
					}
6669
				}
6670
			}
6671
6672
			if ( $this->has_paid_plan() ) {
6673
				switch ( $plan_change ) {
6674
					case 'none':
6675
						if ( ! $background && is_admin() ) {
6676
							$plan = $this->is_trial() ?
6677
								$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...
6678
								$this->_site->plan;
6679
6680
							$this->_admin_notices->add(
6681
								sprintf(
6682
									__fs( 'plan-did-not-change-message', $this->_slug ) . ' ' .
6683
									sprintf(
6684
										'<a href="%s">%s</a>',
6685
										$this->contact_url(
6686
											'bug',
6687
											sprintf( __fs( 'plan-did-not-change-email-message', $this->_slug ),
6688
												strtoupper( $plan->name )
6689
											)
6690
										),
6691
										__fs( 'contact-us-here', $this->_slug )
6692
									),
6693
									'<i>' . $plan->title . ( $this->is_trial() ? ' ' . __fs( 'trial', $this->_slug ) : '' ) . '</i>'
6694
								),
6695
								__fs( 'hmm', $this->_slug ) . '...',
6696
								'error'
6697
							);
6698
						}
6699
						break;
6700
					case 'upgraded':
6701
						$this->_admin_notices->add_sticky(
6702
							sprintf(
6703
								__fs( 'plan-upgraded-message', $this->_slug ),
6704
								'<i>' . $this->get_plugin_name() . '</i>'
6705
							) . ( $this->is_premium() ? '' : ' ' . $this->_get_latest_download_link( sprintf(
6706
									__fs( 'download-latest-x-version', $this->_slug ),
6707
									$this->_site->plan->title
6708
								) )
6709
							),
6710
							'plan_upgraded',
6711
							__fs( 'yee-haw', $this->_slug ) . '!'
6712
						);
6713
6714
						$this->_admin_notices->remove_sticky( array(
6715
							'trial_started',
6716
							'trial_promotion',
6717
							'trial_expired',
6718
							'activation_complete',
6719
						) );
6720
						break;
6721
					case 'changed':
6722
						$this->_admin_notices->add_sticky(
6723
							sprintf(
6724
								__fs( 'plan-changed-to-x-message', $this->_slug ),
6725
								$this->_site->plan->title
6726
							),
6727
							'plan_changed'
6728
						);
6729
6730
						$this->_admin_notices->remove_sticky( array(
6731
							'trial_started',
6732
							'trial_promotion',
6733
							'trial_expired',
6734
							'activation_complete',
6735
						) );
6736
						break;
6737
					case 'downgraded':
6738
						$this->_admin_notices->add_sticky(
6739
							sprintf( __fs( 'license-expired-blocking-message', $this->_slug ) ),
6740
							'license_expired',
6741
							__fs( 'hmm', $this->_slug ) . '...'
6742
						);
6743
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
6744
						break;
6745
					case 'expired':
6746
						$this->_admin_notices->add_sticky(
6747
							sprintf( __fs( 'license-expired-non-blocking-message', $this->_slug ), $this->_site->plan->title ),
6748
							'license_expired',
6749
							__fs( 'hmm', $this->_slug ) . '...'
6750
						);
6751
						$this->_admin_notices->remove_sticky( 'plan_upgraded' );
6752
						break;
6753
					case 'trial_started':
6754
						$this->_admin_notices->add_sticky(
6755
							sprintf(
6756
								__fs( 'trial-started-message', $this->_slug ),
6757
								'<i>' . $this->get_plugin_name() . '</i>'
6758
							) . ( $this->is_premium() ? '' : ' ' . $this->_get_latest_download_link( sprintf(
6759
									__fs( 'download-latest-x-version', $this->_slug ),
6760
									$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...
6761
								) ) ),
6762
							'trial_started',
6763
							__fs( 'yee-haw', $this->_slug ) . '!'
6764
						);
6765
6766
						$this->_admin_notices->remove_sticky( array(
6767
							'trial_promotion',
6768
						) );
6769
						break;
6770
					case 'trial_expired':
6771
						$this->_admin_notices->add_sticky(
6772
							__fs( 'trial-expired-message', $this->_slug ),
6773
							'trial_expired',
6774
							__fs( 'hmm', $this->_slug ) . '...'
6775
						);
6776
						$this->_admin_notices->remove_sticky( array(
6777
							'trial_started',
6778
							'trial_promotion',
6779
							'plan_upgraded',
6780
						) );
6781
						break;
6782
				}
6783
			}
6784
6785
			if ( 'none' !== $plan_change ) {
6786
				$this->do_action( 'after_license_change', $plan_change, $this->_site->plan );
6787
			}
6788
		}
6789
6790
		/**
6791
		 * @author Vova Feldman (@svovaf)
6792
		 * @since  1.0.5
6793
		 *
6794
		 * @param bool $background
6795
		 */
6796
		protected function _activate_license( $background = false ) {
6797
			$this->_logger->entrance();
6798
6799
			$license_id = fs_request_get( 'license_id' );
6800
6801
			if ( FS_Plugin_License::is_valid_id( $license_id ) && $license_id == $this->_site->license_id ) {
6802
				// License is already activated.
6803
				return;
6804
			}
6805
6806
			$premium_license = FS_Plugin_License::is_valid_id( $license_id ) ?
6807
				$this->_get_license_by_id( $license_id ) :
6808
				$this->_get_available_premium_license();
6809
6810
			if ( ! is_object( $premium_license ) ) {
6811
				return;
6812
			}
6813
6814
			$api     = $this->get_api_site_scope();
6815
			$license = $api->call( "/licenses/{$premium_license->id}.json", 'put' );
6816
6817
			if ( $this->is_api_error( $license ) ) {
6818
				if ( ! $background ) {
6819
					$this->_admin_notices->add(
6820
						__fs( 'license-activation-failed-message', $this->_slug ) . '<br> ' .
6821
						__fs( 'server-error-message', $this->_slug ) . ' ' . var_export( $license, true ),
6822
						__fs( 'hmm', $this->_slug ) . '...',
6823
						'error'
6824
					);
6825
				}
6826
6827
				return;
6828
			}
6829
6830
			$premium_license = new FS_Plugin_License( $license );
6831
6832
			// Updated site plan.
6833
			$site = $this->get_api_site_scope()->get( '/', true );
6834
			if ( ! $this->is_api_error( $site ) ) {
6835
				$this->_site = new FS_Site( $site );
6836
			}
6837
			$this->_update_site_license( $premium_license );
6838
			$this->_enrich_site_plan( false );
6839
6840
			$this->_store_account();
6841
6842
			if ( ! $background ) {
6843
				$this->_admin_notices->add_sticky(
6844
					__fs( 'license-activated-message', $this->_slug ) .
6845
					( $this->is_premium() ? '' : ' ' . $this->_get_latest_download_link( sprintf(
6846
							__fs( 'download-latest-x-version', $this->_slug ),
6847
							$this->_site->plan->title
6848
						) ) ),
6849
					'license_activated',
6850
					__fs( 'yee-haw', $this->_slug ) . '!'
6851
				);
6852
			}
6853
6854
			$this->_admin_notices->remove_sticky( array(
6855
				'trial_promotion',
6856
				'license_expired',
6857
			) );
6858
		}
6859
6860
		/**
6861
		 * @author Vova Feldman (@svovaf)
6862
		 * @since  1.0.5
6863
		 *
6864
		 * @param bool $show_notice
6865
		 */
6866
		protected function _deactivate_license( $show_notice = true ) {
6867
			$this->_logger->entrance();
6868
6869
			if ( ! is_object( $this->_license ) ) {
6870
				$this->_admin_notices->add(
6871
					sprintf( __fs( 'no-active-license-message', $this->_slug ), $this->_site->plan->title ),
6872
					__fs( 'hmm', $this->_slug ) . '...'
6873
				);
6874
6875
				return;
6876
			}
6877
6878
			$api     = $this->get_api_site_scope();
6879
			$license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' );
6880
6881
			if ( isset( $license->error ) ) {
6882
				$this->_admin_notices->add(
6883
					__fs( 'license-deactivation-failed-message', $this->_slug ) . '<br> ' .
6884
					__fs( 'server-error-message', $this->_slug ) . ' ' . var_export( $license->error, true ),
6885
					__fs( 'hmm', $this->_slug ) . '...',
6886
					'error'
6887
				);
6888
6889
				return;
6890
			}
6891
6892
			// Update license cache.
6893
			for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) {
6894
				if ( $license->id == $this->_licenses[ $i ]->id ) {
6895
					$this->_licenses[ $i ] = new FS_Plugin_License( $license );
6896
				}
6897
			}
6898
6899
			// Updated site plan to default.
6900
			$this->_sync_plans();
6901
			$this->_site->plan->id = $this->_plans[0]->id;
6902
			// Unlink license from site.
6903
			$this->_update_site_license( null );
6904
			$this->_enrich_site_plan( false );
6905
6906
			$this->_store_account();
6907
6908
			if ( $show_notice ) {
6909
				$this->_admin_notices->add(
6910
					sprintf( __fs( 'license-deactivation-message', $this->_slug ), $this->_site->plan->title ),
6911
					__fs( 'ok', $this->_slug )
6912
				);
6913
			}
6914
6915
			$this->_admin_notices->remove_sticky( array(
6916
				'plan_upgraded',
6917
				'license_activated',
6918
			) );
6919
		}
6920
6921
		/**
6922
		 * Site plan downgrade.
6923
		 *
6924
		 * @author Vova Feldman (@svovaf)
6925
		 * @since  1.0.4
6926
		 *
6927
		 * @uses   FS_Api
6928
		 */
6929
		private function _downgrade_site() {
6930
			$this->_logger->entrance();
6931
6932
			$api  = $this->get_api_site_scope();
6933
			$site = $api->call( 'downgrade.json', 'put' );
6934
6935
			$plan_downgraded = false;
6936
			$plan            = false;
6937
			if ( ! isset( $site->error ) ) {
6938
				$prev_plan_id = $this->_site->plan->id;
6939
6940
				// Update new site plan id.
6941
				$this->_site->plan->id = $site->plan_id;
6942
6943
				$plan         = $this->_enrich_site_plan();
6944
				$subscription = $this->_sync_site_subscription( $this->_license );
6945
6946
				// Plan downgraded if plan was changed or subscription was cancelled.
6947
				$plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) ||
6948
				                   ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() );
6949
			} 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...
6950
				// handle different error cases.
6951
6952
			}
6953
6954
			if ( $plan_downgraded ) {
6955
				// Remove previous sticky message about upgrade (if exist).
6956
				$this->_admin_notices->remove_sticky( 'plan_upgraded' );
6957
6958
				$this->_admin_notices->add(
6959
					sprintf( __fs( 'plan-x-downgraded-message', $this->_slug ),
6960
						$plan->title,
6961
						human_time_diff( time(), strtotime( $this->_license->expiration ) )
6962
					)
6963
				);
6964
6965
				// Store site updates.
6966
				$this->_store_site();
6967
			} else {
6968
				$this->_admin_notices->add(
6969
					__fs( 'plan-downgraded-failure-message', $this->_slug ),
6970
					__fs( 'oops', $this->_slug ) . '...',
6971
					'error'
6972
				);
6973
			}
6974
		}
6975
6976
		/**
6977
		 * Cancel site trial.
6978
		 *
6979
		 * @author Vova Feldman (@svovaf)
6980
		 * @since  1.0.9
6981
		 *
6982
		 * @uses   FS_Api
6983
		 */
6984
		private function _cancel_trial() {
6985
			$this->_logger->entrance();
6986
6987
			if ( ! $this->is_trial() ) {
6988
				$this->_admin_notices->add(
6989
					__fs( 'trial-cancel-no-trial-message', $this->_slug ),
6990
					__fs( 'oops', $this->_slug ) . '...',
6991
					'error'
6992
				);
6993
6994
				return;
6995
			}
6996
6997
			$api  = $this->get_api_site_scope();
6998
			$site = $api->call( 'trials.json', 'delete' );
6999
7000
			$trial_cancelled = false;
7001
7002
			if ( ! $this->is_api_error( $site ) ) {
7003
				$prev_trial_ends = $this->_site->trial_ends;
7004
7005
				if ( $this->is_paid_trial() ) {
7006
					$this->_license->expiration   = $site->trial_ends;
7007
					$this->_license->is_cancelled = true;
7008
					$this->_update_site_license( $this->_license );
7009
					$this->_store_licenses();
7010
7011
					// Clear subscription reference.
7012
					$this->_sync_site_subscription( null );
7013
				}
7014
7015
				// Update site info.
7016
				$this->_site = new FS_Site( $site );
7017
				$this->_enrich_site_plan();
7018
7019
				$trial_cancelled = ( $prev_trial_ends != $site->trial_ends );
7020
			} 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...
7021
				// handle different error cases.
7022
7023
			}
7024
7025
			if ( $trial_cancelled ) {
7026
				// Remove previous sticky messages about upgrade or trial (if exist).
7027
				$this->_admin_notices->remove_sticky( array(
7028
					'trial_started',
7029
					'trial_promotion',
7030
					'plan_upgraded',
7031
				) );
7032
7033
				// Store site updates.
7034
				$this->_store_site();
7035
7036
				// Clear trial plan information.
7037
				unset( $this->_storage->trial_plan );
7038
7039
				if ( ! $this->is_addon() ||
7040
				     ! $this->deactivate_premium_only_addon_without_license( true )
7041
				) {
7042
					$this->_admin_notices->add(
7043
						sprintf( __fs( 'trial-cancel-message', $this->_slug ), $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...
7044
					);
7045
				}
7046
			} else {
7047
				$this->_admin_notices->add(
7048
					__fs( 'trial-cancel-failure-message', $this->_slug ),
7049
					__fs( 'oops', $this->_slug ) . '...',
7050
					'error'
7051
				);
7052
			}
7053
		}
7054
7055
		/**
7056
		 * @author Vova Feldman (@svovaf)
7057
		 * @since  1.0.6
7058
		 *
7059
		 * @param bool|number $plugin_id
7060
		 *
7061
		 * @return bool
7062
		 */
7063
		private function _is_addon_id( $plugin_id ) {
7064
			return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id );
7065
		}
7066
7067
		/**
7068
		 * Check if user eligible to download premium version updates.
7069
		 *
7070
		 * @author Vova Feldman (@svovaf)
7071
		 * @since  1.0.6
7072
		 *
7073
		 * @return bool
7074
		 */
7075
		private function _can_download_premium() {
7076
			return $this->has_active_license() ||
7077
			       ( $this->is_trial() && ! $this->get_trial_plan()->is_free() );
7078
		}
7079
7080
		/**
7081
		 *
7082
		 * @author Vova Feldman (@svovaf)
7083
		 * @since  1.0.6
7084
		 *
7085
		 * @param bool|number $addon_id
7086
		 * @param string      $type "json" or "zip"
7087
		 *
7088
		 * @return string
7089
		 */
7090
		private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) {
7091
7092
			$is_addon = $this->_is_addon_id( $addon_id );
7093
7094
			$is_premium = null;
7095
			if ( ! $is_addon ) {
7096
				$is_premium = $this->_can_download_premium();
7097
			} else if ( $this->is_addon_activated( $addon_id ) ) {
7098
				$is_premium = self::get_instance_by_id( $addon_id )->_can_download_premium();
7099
			}
7100
7101
			return // If add-on, then append add-on ID.
7102
				( $is_addon ? "/addons/$addon_id" : '' ) .
7103
				'/updates/latest.' . $type .
7104
				// If add-on and not yet activated, try to fetch based on server licensing.
7105
				( is_bool( $is_premium ) ? '?is_premium=' . json_encode( $is_premium ) : '' );
7106
		}
7107
7108
		/**
7109
		 * @author Vova Feldman (@svovaf)
7110
		 * @since  1.0.4
7111
		 *
7112
		 * @param bool|number $addon_id
7113
		 * @param bool        $flush Since 1.1.7.3
7114
		 *
7115
		 * @return object|false Plugin latest tag info.
7116
		 */
7117
		function _fetch_latest_version( $addon_id = false, $flush = true ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7118
			$this->_logger->entrance();
7119
7120
			/**
7121
			 * Check for plugin updates from Freemius only if opted-in.
7122
			 *
7123
			 * @since 1.1.7.3
7124
			 */
7125
			if ( ! $this->is_registered() ) {
7126
				return false;
7127
			}
7128
7129
			$tag = $this->get_api_site_scope()->get(
7130
				$this->_get_latest_version_endpoint( $addon_id, 'json' ),
7131
				$flush
7132
			);
7133
7134
			$latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get';
7135
7136
			$this->_logger->departure( 'Latest version ' . $latest_version );
7137
7138
			return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false;
7139
		}
7140
7141
		#region Download Plugin ------------------------------------------------------------------
7142
7143
		/**
7144
		 * Download latest plugin version, based on plan.
7145
		 * The download will be fetched via the API first.
7146
		 *
7147
		 * @author Vova Feldman (@svovaf)
7148
		 * @since  1.0.4
7149
		 *
7150
		 * @param bool|number $plugin_id
7151
		 *
7152
		 * @uses   FS_Api
7153
		 *
7154
		 * @deprecated
7155
		 */
7156
		private function _download_latest( $plugin_id = false ) {
7157
			$this->_logger->entrance();
7158
7159
			$is_addon = $this->_is_addon_id( $plugin_id );
7160
7161
			$is_premium = $this->_can_download_premium();
7162
7163
			$latest = $this->get_api_site_scope()->call(
7164
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
7165
			);
7166
7167
			$slug = $this->_slug;
7168
			if ( $is_addon ) {
7169
				$addon = $this->get_addon( $plugin_id );
7170
				$slug  = is_object( $addon ) ? $addon->slug : 'addon';
7171
			}
7172
7173
			if ( ! is_object( $latest ) ) {
7174
				header( "Content-Type: application/zip" );
7175
				header( "Content-Disposition: attachment; filename={$slug}" . ( ! $is_addon && $is_premium ? '-premium' : '' ) . ".zip" );
7176
				header( "Content-Length: " . strlen( $latest ) );
7177
				echo $latest;
7178
7179
				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...
7180
			}
7181
		}
7182
7183
		/**
7184
		 * Download latest plugin version, based on plan.
7185
		 *
7186
		 * Not like _download_latest(), this will redirect the page
7187
		 * to secure download url to prevent dual download (from FS to WP server,
7188
		 * and then from WP server to the client / browser).
7189
		 *
7190
		 * @author Vova Feldman (@svovaf)
7191
		 * @since  1.0.9
7192
		 *
7193
		 * @param bool|number $plugin_id
7194
		 *
7195
		 * @uses   FS_Api
7196
		 * @uses   wp_redirect()
7197
		 */
7198
		private function _download_latest_directly( $plugin_id = false ) {
7199
			$this->_logger->entrance();
7200
7201
			wp_redirect( $this->_get_latest_download_api_url( $plugin_id ) );
7202
		}
7203
7204
		/**
7205
		 * Get latest plugin FS API download URL.
7206
		 *
7207
		 * @author Vova Feldman (@svovaf)
7208
		 * @since  1.0.9
7209
		 *
7210
		 * @param bool|number $plugin_id
7211
		 *
7212
		 * @return string
7213
		 */
7214
		private function _get_latest_download_api_url( $plugin_id = false ) {
7215
			$this->_logger->entrance();
7216
7217
			return $this->get_api_site_scope()->get_signed_url(
7218
				$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
7219
			);
7220
		}
7221
7222
		/**
7223
		 * Get latest plugin download link.
7224
		 *
7225
		 * @author Vova Feldman (@svovaf)
7226
		 * @since  1.0.9
7227
		 *
7228
		 * @param string      $label
7229
		 * @param bool|number $plugin_id
7230
		 *
7231
		 * @return string
7232
		 */
7233
		private function _get_latest_download_link( $label, $plugin_id = false ) {
7234
			return sprintf(
7235
				'<a target="_blank" href="%s">%s</a>',
7236
				$this->_get_latest_download_local_url( $plugin_id ),
7237
				$label
7238
			);
7239
		}
7240
7241
		/**
7242
		 * Get latest plugin download local URL.
7243
		 *
7244
		 * @author Vova Feldman (@svovaf)
7245
		 * @since  1.0.9
7246
		 *
7247
		 * @param bool|number $plugin_id
7248
		 *
7249
		 * @return string
7250
		 */
7251
		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...
7252
			// Add timestamp to protect from caching.
7253
			$params = array( 'ts' => WP_FS__SCRIPT_START_TIME );
7254
7255
			if ( ! empty( $plugin_id ) ) {
7256
				$params['plugin_id'] = $plugin_id;
7257
			}
7258
7259
			return $this->get_account_url( 'download_latest', $params );
7260
		}
7261
7262
		#endregion Download Plugin ------------------------------------------------------------------
7263
7264
		/**
7265
		 * @author Vova Feldman (@svovaf)
7266
		 * @since  1.0.4
7267
		 *
7268
		 * @uses   FS_Api
7269
		 *
7270
		 * @param bool        $background Hints the method if it's a background updates check. If false, it means that
7271
		 *                                was initiated by the admin.
7272
		 * @param bool|number $plugin_id
7273
		 * @param bool        $flush      Since 1.1.7.3
7274
		 */
7275
		private function _check_updates( $background = false, $plugin_id = false, $flush = true ) {
7276
			$this->_logger->entrance();
7277
7278
			// Check if there's a newer version for download.
7279
			$new_version = $this->_fetch_newer_version( $plugin_id, $flush );
7280
7281
			$update = null;
7282
			if ( is_object( $new_version ) ) {
7283
				$update = new FS_Plugin_Tag( $new_version );
7284
7285
				if ( ! $background ) {
7286
					$this->_admin_notices->add(
7287
						sprintf(
7288
							__fs( 'version-x-released', $this->_slug ) . ' ' . __fs( 'please-download-x', $this->_slug ),
7289
							$update->version,
7290
							sprintf(
7291
								'<a href="%s" target="_blank">%s</a>',
7292
								$this->get_account_url( 'download_latest' ),
7293
								sprintf( __fs( 'latest-x-version', $this->_slug ), $this->_site->plan->title )
7294
							)
7295
						),
7296
						__fs( 'new', $this->_slug ) . '!'
7297
					);
7298
				}
7299
			} else if ( false === $new_version && ! $background ) {
7300
				$this->_admin_notices->add(
7301
					__fs( 'you-have-latest', $this->_slug ),
7302
					__fs( 'you-are-good', $this->_slug )
7303
				);
7304
			}
7305
7306
			$this->_store_update( $update, true, $plugin_id );
7307
		}
7308
7309
		/**
7310
		 * @author Vova Feldman (@svovaf)
7311
		 * @since  1.0.4
7312
		 *
7313
		 * @param bool $flush Since 1.1.7.3 by default add 24 hour cache.
7314
		 *
7315
		 * @return FS_Plugin[]
7316
		 *
7317
		 * @uses   FS_Api
7318
		 */
7319
		private function _sync_addons( $flush = false ) {
7320
			$this->_logger->entrance();
7321
7322
			$result = $this->get_api_site_or_plugin_scope()->get( '/addons.json?enriched=true', $flush );
7323
7324
			$addons = array();
7325
			if ( ! $this->is_api_error( $result ) ) {
7326
				for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) {
7327
					$addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] );
7328
				}
7329
7330
				$this->_store_addons( $addons, true );
7331
			}
7332
7333
			return $addons;
7334
		}
7335
7336
		/**
7337
		 * Handle user email update.
7338
		 *
7339
		 * @author Vova Feldman (@svovaf)
7340
		 * @since  1.0.3
7341
		 * @uses   FS_Api
7342
		 *
7343
		 * @param string $new_email
7344
		 *
7345
		 * @return object
7346
		 */
7347
		private function _update_email( $new_email ) {
7348
			$this->_logger->entrance();
7349
7350
7351
			$api  = $this->get_api_user_scope();
7352
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array(
7353
				'email'                   => $new_email,
7354
				'after_email_confirm_url' => $this->_get_admin_page_url(
7355
					'account',
7356
					array( 'fs_action' => 'sync_user' )
7357
				),
7358
			) );
7359
7360
			if ( ! isset( $user->error ) ) {
7361
				$this->_user->email       = $user->email;
7362
				$this->_user->is_verified = $user->is_verified;
7363
				$this->_store_user();
7364
			} 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...
7365
				// handle different error cases.
7366
7367
			}
7368
7369
			return $user;
7370
		}
7371
7372
		/**
7373
		 * @author Vova Feldman (@svovaf)
7374
		 * @since  1.1.1
7375
		 *
7376
		 * @param mixed $result
7377
		 *
7378
		 * @return bool Is API result contains an error.
7379
		 */
7380
		private function is_api_error( $result ) {
7381
			return ( is_object( $result ) && isset( $result->error ) ) ||
7382
			       is_string( $result );
7383
		}
7384
7385
		/**
7386
		 * Start install ownership change.
7387
		 *
7388
		 * @author Vova Feldman (@svovaf)
7389
		 * @since  1.1.1
7390
		 * @uses   FS_Api
7391
		 *
7392
		 * @param string $candidate_email
7393
		 *
7394
		 * @return bool Is ownership change successfully initiated.
7395
		 */
7396
		private function init_change_owner( $candidate_email ) {
7397
			$this->_logger->entrance();
7398
7399
			$api    = $this->get_api_site_scope();
7400
			$result = $api->call( "/users/{$this->_user->id}.json", 'put', array(
7401
				'email'             => $candidate_email,
7402
				'after_confirm_url' => $this->_get_admin_page_url(
7403
					'account',
7404
					array( 'fs_action' => 'change_owner' )
7405
				),
7406
			) );
7407
7408
			return ! $this->is_api_error( $result );
7409
		}
7410
7411
		/**
7412
		 * Handle install ownership change.
7413
		 *
7414
		 * @author Vova Feldman (@svovaf)
7415
		 * @since  1.1.1
7416
		 * @uses   FS_Api
7417
		 *
7418
		 * @return bool Was ownership change successfully complete.
7419
		 */
7420
		private function complete_change_owner() {
7421
			$this->_logger->entrance();
7422
7423
			$site_result = $this->get_api_site_scope( true )->get();
7424
			$site        = new FS_Site( $site_result );
7425
			$this->_site = $site;
7426
7427
			$user     = new FS_User();
7428
			$user->id = fs_request_get( 'user_id' );
7429
7430
			// Validate install's user and given user.
7431
			if ( $user->id != $this->_site->user_id ) {
7432
				return false;
7433
			}
7434
7435
			$user->public_key = fs_request_get( 'user_public_key' );
7436
			$user->secret_key = fs_request_get( 'user_secret_key' );
7437
7438
			// Fetch new user information.
7439
			$this->_user = $user;
7440
			$user_result = $this->get_api_user_scope( true )->get();
7441
			$user        = new FS_User( $user_result );
7442
			$this->_user = $user;
7443
7444
			$this->_set_account( $user, $site );
7445
7446
			return true;
7447
		}
7448
7449
		/**
7450
		 * Handle user name update.
7451
		 *
7452
		 * @author Vova Feldman (@svovaf)
7453
		 * @since  1.0.9
7454
		 * @uses   FS_Api
7455
		 *
7456
		 * @return object
7457
		 */
7458
		private function update_user_name() {
7459
			$this->_logger->entrance();
7460
			$name = fs_request_get( 'fs_user_name_' . $this->_slug, '' );
7461
7462
			$api  = $this->get_api_user_scope();
7463
			$user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array(
7464
				'name' => $name,
7465
			) );
7466
7467
			if ( ! isset( $user->error ) ) {
7468
				$this->_user->first = $user->first;
7469
				$this->_user->last  = $user->last;
7470
				$this->_store_user();
7471
			} 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...
7472
				// handle different error cases.
7473
7474
			}
7475
7476
			return $user;
7477
		}
7478
7479
		/**
7480
		 * Verify user email.
7481
		 *
7482
		 * @author Vova Feldman (@svovaf)
7483
		 * @since  1.0.3
7484
		 * @uses   FS_Api
7485
		 */
7486
		private function verify_email() {
7487
			$this->_handle_account_user_sync();
7488
7489
			if ( $this->_user->is_verified() ) {
7490
				return;
7491
			}
7492
7493
			$api    = $this->get_api_site_scope();
7494
			$result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array(
7495
				'after_email_confirm_url' => $this->_get_admin_page_url(
7496
					'account',
7497
					array( 'fs_action' => 'sync_user' )
7498
				)
7499
			) );
7500
7501
			if ( ! isset( $result->error ) ) {
7502
				$this->_admin_notices->add( sprintf(
7503
					__fs( 'verification-email-sent-message', $this->_slug ),
7504
					sprintf( '<a href="mailto:%1s">%2s</a>', esc_url( $this->_user->email ), $this->_user->email )
7505
				) );
7506
			} 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...
7507
				// handle different error cases.
7508
7509
			}
7510
		}
7511
7512
		/**
7513
		 * @author Vova Feldman (@svovaf)
7514
		 * @since  1.1.2
7515
		 *
7516
		 * @return string
7517
		 */
7518
		private function get_activation_url() {
7519
			return $this->apply_filters( 'connect_url', $this->_get_admin_page_url() );
7520
		}
7521
7522
		/**
7523
		 * @author Vova Feldman (@svovaf)
7524
		 * @since  1.1.3
7525
		 *
7526
		 * @param string $filter Filter name.
7527
		 *
7528
		 * @return string
7529
		 */
7530
		private function get_after_activation_url( $filter ) {
7531
			$first_time_path = $this->_menu->get_first_time_path();
7532
7533
			return $this->apply_filters(
7534
				$filter,
7535
				empty( $first_time_path ) ?
7536
					$this->_get_admin_page_url() :
7537
					$first_time_path
7538
			);
7539
		}
7540
7541
		/**
7542
		 * Handle account page updates / edits / actions.
7543
		 *
7544
		 * @author Vova Feldman (@svovaf)
7545
		 * @since  1.0.2
7546
		 *
7547
		 */
7548
		private function _handle_account_edits() {
7549
			if ( ! current_user_can( 'activate_plugins' ) ) {
7550
				return;
7551
			}
7552
7553
			$plugin_id = fs_request_get( 'plugin_id', $this->get_id() );
7554
			$action    = fs_get_action();
7555
7556
			switch ( $action ) {
7557
				case 'delete_account':
7558
					check_admin_referer( $action );
7559
7560
					if ( $plugin_id == $this->get_id() ) {
7561
						$this->delete_account_event();
7562
7563
						// Clear user and site.
7564
						$this->_site = null;
7565
						$this->_user = null;
7566
7567
						if ( fs_redirect( $this->get_activation_url() ) ) {
7568
							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...
7569
						}
7570
					} else {
7571
						if ( $this->is_addon_activated( $plugin_id ) ) {
7572
							$fs_addon = self::get_instance_by_id( $plugin_id );
7573
							$fs_addon->delete_account_event();
7574
7575
							if ( fs_redirect( $this->_get_admin_page_url( 'account' ) ) ) {
7576
								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...
7577
							}
7578
						}
7579
					}
7580
7581
					return;
7582
7583
				case 'downgrade_account':
7584
					check_admin_referer( $action );
7585
					$this->_downgrade_site();
7586
7587
					return;
7588
7589
				case 'activate_license':
7590
					check_admin_referer( $action );
7591
7592
					if ( $plugin_id == $this->get_id() ) {
7593
						$this->_activate_license();
7594
					} else {
7595
						if ( $this->is_addon_activated( $plugin_id ) ) {
7596
							$fs_addon = self::get_instance_by_id( $plugin_id );
7597
							$fs_addon->_activate_license();
7598
						}
7599
					}
7600
7601
					return;
7602
7603
				case 'deactivate_license':
7604
					check_admin_referer( $action );
7605
7606
					if ( $plugin_id == $this->get_id() ) {
7607
						$this->_deactivate_license();
7608
					} else {
7609
						if ( $this->is_addon_activated( $plugin_id ) ) {
7610
							$fs_addon = self::get_instance_by_id( $plugin_id );
7611
							$fs_addon->_deactivate_license();
7612
						}
7613
					}
7614
7615
					return;
7616
7617
				case 'check_updates':
7618
					check_admin_referer( $action );
7619
					$this->_check_updates();
7620
7621
					return;
7622
7623
				case 'change_owner':
7624
					$state = fs_request_get( 'state', 'init' );
7625
					switch ( $state ) {
7626
						case 'init':
7627
							$candidate_email = fs_request_get( 'candidate_email', '' );
7628
7629
							if ( $this->init_change_owner( $candidate_email ) ) {
7630
								$this->_admin_notices->add( sprintf( __fs( 'change-owner-request-sent-x', $this->_slug ), '<b>' . $this->_user->email . '</b>' ) );
7631
							}
7632
							break;
7633
						case 'owner_confirmed':
7634
							$candidate_email = fs_request_get( 'candidate_email', '' );
7635
7636
							$this->_admin_notices->add( sprintf( __fs( 'change-owner-request_owner-confirmed', $this->_slug ), '<b>' . $candidate_email . '</b>' ) );
7637
							break;
7638
						case 'candidate_confirmed':
7639
							if ( $this->complete_change_owner() ) {
7640
								$this->_admin_notices->add_sticky(
7641
									sprintf( __fs( 'change-owner-request_candidate-confirmed', $this->_slug ), '<b>' . $this->_user->email . '</b>' ),
7642
									'ownership_changed',
7643
									__fs( 'congrats', $this->_slug ) . '!'
7644
								);
7645
							} 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...
7646
								// @todo Handle failed ownership change message.
7647
							}
7648
							break;
7649
					}
7650
7651
					return;
7652
7653
				case 'update_email':
7654
					check_admin_referer( 'update_email' );
7655
7656
					$new_email = fs_request_get( 'fs_email_' . $this->_slug, '' );
7657
					$result    = $this->_update_email( $new_email );
7658
7659
					if ( isset( $result->error ) ) {
7660
						switch ( $result->error->code ) {
7661
							case 'user_exist':
7662
								$this->_admin_notices->add(
7663
									__fs( 'user-exist-message', $this->_slug ) . ' ' .
7664
									sprintf( __fs( 'user-exist-message_ownership', $this->_slug ), '<b>' . $new_email . '</b>' ) .
7665
									sprintf(
7666
										'<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
7667
										$this->get_account_url( 'change_owner', array(
7668
											'state'           => 'init',
7669
											'candidate_email' => $new_email
7670
										) ),
7671
										__fs( 'change-ownership', $this->_slug )
7672
									),
7673
									__fs( 'oops', $this->_slug ) . '...',
7674
									'error'
7675
								);
7676
								break;
7677
						}
7678
					} else {
7679
						$this->_admin_notices->add( __fs( 'email-updated-message', $this->_slug ) );
7680
					}
7681
7682
					return;
7683
7684
				case 'update_user_name':
7685
					check_admin_referer( 'update_user_name' );
7686
7687
					$result = $this->update_user_name();
7688
7689
					if ( isset( $result->error ) ) {
7690
						$this->_admin_notices->add(
7691
							__fs( 'name-update-failed-message', $this->_slug ),
7692
							__fs( 'oops', $this->_slug ) . '...',
7693
							'error'
7694
						);
7695
					} else {
7696
						$this->_admin_notices->add( __fs( 'name-updated-message', $this->_slug ) );
7697
					}
7698
7699
					return;
7700
7701
				#region Actions that might be called from external links (e.g. email)
7702
7703
				case 'cancel_trial':
7704
					if ( $plugin_id == $this->get_id() ) {
7705
						$this->_cancel_trial();
7706
					} else {
7707
						if ( $this->is_addon_activated( $plugin_id ) ) {
7708
							$fs_addon = self::get_instance_by_id( $plugin_id );
7709
							$fs_addon->_cancel_trial();
7710
						}
7711
					}
7712
7713
					return;
7714
7715
				case 'verify_email':
7716
					$this->verify_email();
7717
7718
					return;
7719
7720
				case 'sync_user':
7721
					$this->_handle_account_user_sync();
7722
7723
					return;
7724
7725
				case $this->_slug . '_sync_license':
7726
					$this->_sync_license();
7727
7728
					return;
7729
7730
				case 'download_latest':
7731
					$this->_download_latest_directly( $plugin_id );
7732
7733
					return;
7734
7735
				#endregion
7736
			}
7737
7738
			if ( WP_FS__IS_POST_REQUEST ) {
7739
				$properties = array( 'site_secret_key', 'site_id', 'site_public_key' );
7740
				foreach ( $properties as $p ) {
7741
					if ( 'update_' . $p === $action ) {
7742
						check_admin_referer( $action );
7743
7744
						$this->_logger->log( $action );
7745
7746
						$site_property                      = substr( $p, strlen( 'site_' ) );
7747
						$site_property_value                = fs_request_get( 'fs_' . $p . '_' . $this->_slug, '' );
7748
						$this->get_site()->{$site_property} = $site_property_value;
7749
7750
						// Store account after modification.
7751
						$this->_store_site();
7752
7753
						$this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value );
7754
7755
						$this->_admin_notices->add( sprintf(
7756
							__fs( 'x-updated', $this->_slug ),
7757
							'<b>' . str_replace( '_', ' ', $p ) . '</b>' ) );
7758
7759
						return;
7760
					}
7761
				}
7762
			}
7763
		}
7764
7765
		/**
7766
		 * Account page resources load.
7767
		 *
7768
		 * @author Vova Feldman (@svovaf)
7769
		 * @since  1.0.6
7770
		 */
7771
		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...
7772
			$this->_logger->entrance();
7773
7774
			$this->_logger->info( var_export( $_REQUEST, true ) );
7775
7776
			fs_enqueue_local_style( 'fs_account', '/admin/account.css' );
7777
7778
			if ( $this->has_addons() ) {
7779
				wp_enqueue_script( 'plugin-install' );
7780
				add_thickbox();
7781
7782
				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...
7783
					$classes .= ' plugins-php';
7784
7785
					return $classes;
7786
				}
7787
7788
				add_filter( 'admin_body_class', 'fs_addons_body_class' );
7789
			}
7790
7791
			if ( $this->has_paid_plan() &&
7792
			     ! $this->has_any_license() &&
7793
			     ! $this->is_sync_executed()
7794
			) {
7795
				/**
7796
				 * If no licenses found and no sync job was executed during the last 24 hours,
7797
				 * just execute the sync job right away (blocking execution).
7798
				 *
7799
				 * @since 1.1.7.3
7800
				 */
7801
				$this->run_manual_sync();
7802
			}
7803
7804
			$this->_handle_account_edits();
7805
7806
			$this->do_action( 'account_page_load_before_departure' );
7807
		}
7808
7809
		/**
7810
		 * Render account page.
7811
		 *
7812
		 * @author Vova Feldman (@svovaf)
7813
		 * @since  1.0.0
7814
		 */
7815
		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...
7816
			$this->_logger->entrance();
7817
7818
			$vars = array( 'slug' => $this->_slug );
7819
			fs_require_once_template( 'account.php', $vars );
7820
		}
7821
7822
		/**
7823
		 * Render account connect page.
7824
		 *
7825
		 * @author Vova Feldman (@svovaf)
7826
		 * @since  1.0.7
7827
		 */
7828
		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...
7829
			$this->_logger->entrance();
7830
7831
			$vars = array( 'slug' => $this->_slug );
7832
			fs_require_once_template( 'connect.php', $vars );
7833
		}
7834
7835
		/**
7836
		 * Load required resources before add-ons page render.
7837
		 *
7838
		 * @author Vova Feldman (@svovaf)
7839
		 * @since  1.0.6
7840
		 */
7841
		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...
7842
			$this->_logger->entrance();
7843
7844
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
7845
7846
			wp_enqueue_script( 'plugin-install' );
7847
			add_thickbox();
7848
7849
			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 (L7782-7786) 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...
7850
				$classes .= ' plugins-php';
7851
7852
				return $classes;
7853
			}
7854
7855
			add_filter( 'admin_body_class', 'fs_addons_body_class' );
7856
7857
			if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) {
7858
				$this->_admin_notices->add(
7859
					sprintf( __fs( 'addons-info-external-message', $this->_slug ), '<b>' . $this->get_plugin_name() . '</b>' ),
7860
					__fs( 'heads-up', $this->_slug ),
7861
					'update-nag'
7862
				);
7863
			}
7864
		}
7865
7866
		/**
7867
		 * Render add-ons page.
7868
		 *
7869
		 * @author Vova Feldman (@svovaf)
7870
		 * @since  1.0.6
7871
		 */
7872
		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...
7873
			$this->_logger->entrance();
7874
7875
			$vars = array( 'slug' => $this->_slug );
7876
			fs_require_once_template( 'add-ons.php', $vars );
7877
		}
7878
7879
		/* Pricing & Upgrade
7880
		------------------------------------------------------------------------------------------------------------------*/
7881
		/**
7882
		 * Render pricing page.
7883
		 *
7884
		 * @author Vova Feldman (@svovaf)
7885
		 * @since  1.0.0
7886
		 */
7887
		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...
7888
			$this->_logger->entrance();
7889
7890
			$vars = array( 'slug' => $this->_slug );
7891
7892
			if ( 'true' === fs_request_get( 'checkout', false ) ) {
7893
				fs_require_once_template( 'checkout.php', $vars );
7894
			} else {
7895
				fs_require_once_template( 'pricing.php', $vars );
7896
			}
7897
		}
7898
7899
		#region Contact Us ------------------------------------------------------------------
7900
7901
		/**
7902
		 * Render contact-us page.
7903
		 *
7904
		 * @author Vova Feldman (@svovaf)
7905
		 * @since  1.0.3
7906
		 */
7907
		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...
7908
			$this->_logger->entrance();
7909
7910
			$vars = array( 'slug' => $this->_slug );
7911
			fs_require_once_template( 'contact.php', $vars );
7912
		}
7913
7914
		#endregion ------------------------------------------------------------------
7915
7916
		/**
7917
		 * Hide all admin notices to prevent distractions.
7918
		 *
7919
		 * @author Vova Feldman (@svovaf)
7920
		 * @since  1.0.3
7921
		 *
7922
		 * @uses   remove_all_actions()
7923
		 */
7924
		private static function _hide_admin_notices() {
7925
			remove_all_actions( 'admin_notices' );
7926
			remove_all_actions( 'network_admin_notices' );
7927
			remove_all_actions( 'all_admin_notices' );
7928
			remove_all_actions( 'user_admin_notices' );
7929
		}
7930
7931
		static function _clean_admin_content_section_hook() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7932
			self::_hide_admin_notices();
7933
7934
			// Hide footer.
7935
			echo '<style>#wpfooter { display: none !important; }</style>';
7936
		}
7937
7938
		/**
7939
		 * Attach to admin_head hook to hide all admin notices.
7940
		 *
7941
		 * @author Vova Feldman (@svovaf)
7942
		 * @since  1.0.3
7943
		 */
7944
		static function _clean_admin_content_section() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
7945
			add_action( 'admin_head', 'Freemius::_clean_admin_content_section_hook' );
7946
		}
7947
7948
		/* CSS & JavaScript
7949
		------------------------------------------------------------------------------------------------------------------*/
7950
		/*		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...
7951
					$url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src );
7952
7953
					$this->_logger->entrance( 'script = ' . $url );
7954
7955
					wp_enqueue_script( $handle, $url );
7956
				}*/
7957
7958
		/* SDK
7959
		------------------------------------------------------------------------------------------------------------------*/
7960
		private $_user_api;
7961
7962
		/**
7963
		 *
7964
		 * @author Vova Feldman (@svovaf)
7965
		 * @since  1.0.2
7966
		 *
7967
		 * @param bool $flush
7968
		 *
7969
		 * @return FS_Api
7970
		 */
7971
		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...
7972
			if ( ! isset( $this->_user_api ) || $flush ) {
7973
				$this->_user_api = FS_Api::instance(
7974
					$this->_slug,
7975
					'user',
7976
					$this->_user->id,
7977
					$this->_user->public_key,
7978
					! $this->is_live(),
7979
					$this->_user->secret_key
7980
				);
7981
			}
7982
7983
			return $this->_user_api;
7984
		}
7985
7986
		private $_site_api;
7987
7988
		/**
7989
		 *
7990
		 * @author Vova Feldman (@svovaf)
7991
		 * @since  1.0.2
7992
		 *
7993
		 * @param bool $flush
7994
		 *
7995
		 * @return FS_Api
7996
		 */
7997
		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...
7998
			if ( ! isset( $this->_site_api ) || $flush ) {
7999
				$this->_site_api = FS_Api::instance(
8000
					$this->_slug,
8001
					'install',
8002
					$this->_site->id,
8003
					$this->_site->public_key,
8004
					! $this->is_live(),
8005
					$this->_site->secret_key
8006
				);
8007
			}
8008
8009
			return $this->_site_api;
8010
		}
8011
8012
		private $_plugin_api;
8013
8014
		/**
8015
		 * Get plugin public API scope.
8016
		 *
8017
		 * @author Vova Feldman (@svovaf)
8018
		 * @since  1.0.7
8019
		 *
8020
		 * @return FS_Api
8021
		 */
8022
		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...
8023
			if ( ! isset( $this->_plugin_api ) ) {
8024
				$this->_plugin_api = FS_Api::instance(
8025
					$this->_slug,
8026
					'plugin',
8027
					$this->_plugin->id,
8028
					$this->_plugin->public_key,
8029
					! $this->is_live()
8030
				);
8031
			}
8032
8033
			return $this->_plugin_api;
8034
		}
8035
8036
		/**
8037
		 * Get site API scope object (fallback to public plugin scope when not registered).
8038
		 *
8039
		 * @author Vova Feldman (@svovaf)
8040
		 * @since  1.0.7
8041
		 *
8042
		 * @return FS_Api
8043
		 */
8044
		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...
8045
			return $this->is_registered() ?
8046
				$this->get_api_site_scope() :
8047
				$this->get_api_plugin_scope();
8048
		}
8049
8050
		/**
8051
		 * Show trial promotional notice (if any trial exist).
8052
		 *
8053
		 * @author Vova Feldman (@svovaf)
8054
		 * @since  1.0.9
8055
		 *
8056
		 * @param $plans
8057
		 */
8058
		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...
8059
			$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...
8060
		}
8061
8062
		/**
8063
		 * Show trial promotional notice (if any trial exist).
8064
		 *
8065
		 * @author Vova Feldman (@svovaf)
8066
		 * @since  1.0.9
8067
		 */
8068
		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...
8069
			// Check if trial already utilized.
8070
			if ( $this->_site->is_trial_utilized() ) {
8071
				return;
8072
			}
8073
8074
			// Check if already paying.
8075
			if ( $this->is_paying() ) {
8076
				return;
8077
			}
8078
8079
			// Check if trial message is already shown.
8080
			if ( $this->_admin_notices->has_sticky( 'trial_promotion' ) ) {
8081
				return;
8082
			}
8083
8084
			$trial_plans       = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
8085
			$trial_plans_count = count( $trial_plans );
8086
8087
			// Check if any of the plans contains trial.
8088
			if ( 0 === $trial_plans_count ) {
8089
				return;
8090
			}
8091
8092
			/**
8093
			 * @var FS_Plugin_Plan $paid_plan
8094
			 */
8095
			$paid_plan            = $trial_plans[0];
8096
			$require_subscription = $paid_plan->is_require_subscription;
8097
			$upgrade_url          = $this->get_trial_url();
8098
			$cc_string            = $require_subscription ?
8099
				sprintf( __fs( 'no-commitment-for-x-days', $this->_slug ), $paid_plan->trial_period ) :
8100
				__fs( 'no-cc-required', $this->_slug ) . '!';
8101
8102
8103
			$total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 );
8104
8105
			if ( $total_paid_plans === $trial_plans_count ) {
8106
				// All paid plans have trials.
8107
				$message = sprintf(
8108
					__fs( 'hey', $this->_slug ) . '! ' . __fs( 'trial-x-promotion-message', $this->_slug ),
8109
					sprintf( '<b>%s</b>', $this->get_plugin_name() ),
8110
					strtolower( __fs( 'awesome', $this->_slug ) ),
8111
					$paid_plan->trial_period
8112
				);
8113
			} else {
8114
				$plans_string = '';
8115
				for ( $i = 0; $i < $trial_plans_count; $i ++ ) {
8116
					$plans_string .= sprintf( '<a href="%s">%s</a>', $upgrade_url, $trial_plans[ $i ]->title );
8117
8118
					if ( $i < $trial_plans_count - 2 ) {
8119
						$plans_string .= ', ';
8120
					} else if ( $i == $trial_plans_count - 2 ) {
8121
						$plans_string .= ' and ';
8122
					}
8123
				}
8124
8125
				// Not all paid plans have trials.
8126
				$message = sprintf(
8127
					__fs( 'hey', $this->_slug ) . '! ' . __fs( 'trial-x-promotion-message', $this->_slug ),
8128
					sprintf( '<b>%s</b>', $this->get_plugin_name() ),
8129
					$plans_string,
8130
					$paid_plan->trial_period
8131
				);
8132
			}
8133
8134
			$message .= ' ' . $cc_string;
8135
8136
			// Add start trial button.
8137
			$message .= ' ' . sprintf(
8138
					'<a style="margin-left: 10px; vertical-align: super;" href="%s"><button class="button button-primary">%s &nbsp;&#10140;</button></a>',
8139
					$upgrade_url,
8140
					__fs( 'start-free-trial', $this->_slug )
8141
				);
8142
8143
			$this->_admin_notices->add_sticky(
8144
				$this->apply_filters( 'trial_promotion_message', $message ),
8145
				'trial_promotion',
8146
				'',
8147
				'promotion'
8148
			);
8149
8150
			$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...
8151
		}
8152
8153
		/* Action Links
8154
		------------------------------------------------------------------------------------------------------------------*/
8155
		private $_action_links_hooked = false;
8156
		private $_action_links = array();
8157
8158
		/**
8159
		 * @author Vova Feldman (@svovaf)
8160
		 * @since  1.0.0
8161
		 *
8162
		 * @return bool
8163
		 */
8164
		private function is_plugin_action_links_hooked() {
8165
			$this->_logger->entrance( json_encode( $this->_action_links_hooked ) );
8166
8167
			return $this->_action_links_hooked;
8168
		}
8169
8170
		/**
8171
		 * Hook to plugin action links filter.
8172
		 *
8173
		 * @author Vova Feldman (@svovaf)
8174
		 * @since  1.0.0
8175
		 */
8176
		private function hook_plugin_action_links() {
8177
			$this->_logger->entrance();
8178
8179
			$this->_action_links_hooked = true;
8180
8181
			$this->_logger->log( 'Adding action links hooks.' );
8182
8183
			// Add action link to settings page.
8184
			add_filter( 'plugin_action_links_' . $this->_plugin_basename, array(
8185
				&$this,
8186
				'_modify_plugin_action_links_hook'
8187
			), WP_FS__DEFAULT_PRIORITY, 2 );
8188
			add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array(
8189
				&$this,
8190
				'_modify_plugin_action_links_hook'
8191
			), WP_FS__DEFAULT_PRIORITY, 2 );
8192
		}
8193
8194
		/**
8195
		 * Add plugin action link.
8196
		 *
8197
		 * @author Vova Feldman (@svovaf)
8198
		 * @since  1.0.0
8199
		 *
8200
		 * @param      $label
8201
		 * @param      $url
8202
		 * @param bool $external
8203
		 * @param int  $priority
8204
		 * @param bool $key
8205
		 */
8206
		function add_plugin_action_link( $label, $url, $external = false, $priority = WP_FS__DEFAULT_PRIORITY, $key = false ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8207
			$this->_logger->entrance();
8208
8209
			if ( ! isset( $this->_action_links[ $priority ] ) ) {
8210
				$this->_action_links[ $priority ] = array();
8211
			}
8212
8213
			if ( false === $key ) {
8214
				$key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) );
8215
			}
8216
8217
			$this->_action_links[ $priority ][] = array(
8218
				'label'    => $label,
8219
				'href'     => $url,
8220
				'key'      => $key,
8221
				'external' => $external
8222
			);
8223
		}
8224
8225
		/**
8226
		 * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection.
8227
		 *
8228
		 * @author Vova Feldman (@svovaf)
8229
		 * @since  1.0.0
8230
		 */
8231
		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...
8232
			$this->_logger->entrance();
8233
8234
			if ( $this->is_registered() ) {
8235
				if ( ! $this->is_paying() && $this->has_paid_plan() ) {
8236
					$this->add_plugin_action_link(
8237
						__fs( 'upgrade', $this->_slug ),
8238
						$this->get_upgrade_url(),
8239
						false,
8240
						7,
8241
						'upgrade'
8242
					);
8243
				}
8244
8245
				if ( $this->has_addons() ) {
8246
					$this->add_plugin_action_link(
8247
						__fs( 'add-ons', $this->_slug ),
8248
						$this->_get_admin_page_url( 'addons' ),
8249
						false,
8250
						9,
8251
						'addons'
8252
					);
8253
				}
8254
			}
8255
		}
8256
8257
		/**
8258
		 * Forward page to activation page.
8259
		 *
8260
		 * @author Vova Feldman (@svovaf)
8261
		 * @since  1.0.3
8262
		 */
8263
		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...
8264
			$url       = false;
8265
			$plugin_fs = false;
8266
8267
			if ( ! $this->is_addon() ) {
8268
				$first_time_path = $this->_menu->get_first_time_path();
8269
				$plugin_fs       = $this;
8270
				$url             = $plugin_fs->is_activation_mode() ?
8271
					$plugin_fs->get_activation_url() :
8272
					( empty( $first_time_path ) ?
8273
						$this->_get_admin_page_url() :
8274
						$first_time_path );
8275
			} else {
8276
				if ( $this->is_parent_plugin_installed() ) {
8277
					$plugin_fs = self::get_parent_instance();
8278
				}
8279
8280
				if ( is_object( $plugin_fs ) ) {
8281
					if ( ! $plugin_fs->is_registered() ) {
8282
						// Forward to parent plugin connect when parent not registered.
8283
						$url = $plugin_fs->get_activation_url();
8284
					} else {
8285
						// Forward to account page.
8286
						$url = $plugin_fs->_get_admin_page_url( 'account' );
8287
					}
8288
				}
8289
			}
8290
8291
			if ( is_string( $url ) ) {
8292
				fs_redirect( $url );
8293
				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...
8294
			}
8295
		}
8296
8297
		/**
8298
		 * Modify plugin's page action links collection.
8299
		 *
8300
		 * @author Vova Feldman (@svovaf)
8301
		 * @since  1.0.0
8302
		 *
8303
		 * @param array $links
8304
		 * @param       $file
8305
		 *
8306
		 * @return array
8307
		 */
8308
		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...
8309
			$this->_logger->entrance();
8310
8311
			$passed_deactivate = false;
8312
			$deactivate_link   = '';
8313
			$before_deactivate = array();
8314
			$after_deactivate  = array();
8315
			foreach ( $links as $key => $link ) {
8316
				if ( 'deactivate' === $key ) {
8317
					$deactivate_link   = $link;
8318
					$passed_deactivate = true;
8319
					continue;
8320
				}
8321
8322
				if ( ! $passed_deactivate ) {
8323
					$before_deactivate[ $key ] = $link;
8324
				} else {
8325
					$after_deactivate[ $key ] = $link;
8326
				}
8327
			}
8328
8329
			ksort( $this->_action_links );
8330
8331
			foreach ( $this->_action_links as $new_links ) {
8332
				foreach ( $new_links as $link ) {
8333
					$before_deactivate[ $link['key'] ] = '<a href="' . $link['href'] . '"' . ( $link['external'] ? ' target="_blank"' : '' ) . '>' . $link['label'] . '</a>';
8334
				}
8335
			}
8336
8337
			if ( ! empty( $deactivate_link ) ) {
8338
				if ( ! $this->is_paying_or_trial() || $this->is_premium() ) {
8339
					/*
8340
					 * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link.
8341
					 *
8342
					 * If user is paying or in trial and have the free version installed,
8343
					 * assume that the deactivation is for the upgrade process, so this is not needed.
8344
					 */
8345
					$deactivate_link .= '<i class="fs-slug" data-slug="' . $this->_slug . '"></i>';
8346
				}
8347
8348
				// Append deactivation link.
8349
				$before_deactivate['deactivate'] = $deactivate_link;
8350
			}
8351
8352
			return array_merge( $before_deactivate, $after_deactivate );
8353
		}
8354
8355
		/**
8356
		 * Adds admin message.
8357
		 *
8358
		 * @author Vova Feldman (@svovaf)
8359
		 * @since  1.0.4
8360
		 *
8361
		 * @param string $message
8362
		 * @param string $title
8363
		 * @param string $type
8364
		 */
8365
		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...
8366
			$this->_admin_notices->add( $message, $title, $type );
8367
		}
8368
8369
		/**
8370
		 * Adds sticky admin message.
8371
		 *
8372
		 * @author Vova Feldman (@svovaf)
8373
		 * @since  1.1.0
8374
		 *
8375
		 * @param string $message
8376
		 * @param string $id
8377
		 * @param string $title
8378
		 * @param string $type
8379
		 */
8380
		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...
8381
			$this->_admin_notices->add_sticky( $message, $id, $title, $type );
8382
		}
8383
8384
		/* Plugin Auto-Updates (@since 1.0.4)
8385
		------------------------------------------------------------------------------------------------------------------*/
8386
		/**
8387
		 * @var string[]
8388
		 */
8389
		private static $_auto_updated_plugins;
8390
8391
		/**
8392
		 * @todo   TEST IF IT WORKS!!!
8393
		 *
8394
		 * Include plugins for automatic updates based on stored settings.
8395
		 *
8396
		 * @see    http://wordpress.stackexchange.com/questions/131394/how-do-i-exclude-plugins-from-getting-automatically-updated/131404#131404
8397
		 *
8398
		 * @author Vova Feldman (@svovaf)
8399
		 * @since  1.0.4
8400
		 *
8401
		 * @param bool   $update Whether to update (not used for plugins)
8402
		 * @param object $item   The plugin's info
8403
		 *
8404
		 * @return bool
8405
		 */
8406
		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...
8407
			// Before version 3.8.2 the $item was the file name of the plugin,
8408
			// while in 3.8.2 statistics were added (https://core.trac.wordpress.org/changeset/27905).
8409
			$by_slug = ( (int) str_replace( '.', '', get_bloginfo( 'version' ) ) >= 382 );
8410
8411
			if ( ! isset( self::$_auto_updated_plugins ) ) {
8412
				$plugins = self::$_accounts->get_option( 'plugins', array() );
8413
8414
				$identifiers = array();
8415
				foreach ( $plugins as $p ) {
8416
					/**
8417
					 * @var FS_Plugin $p
8418
					 */
8419
					if ( isset( $p->auto_update ) && $p->auto_update ) {
8420
						$identifiers[] = ( $by_slug ? $p->slug : plugin_basename( $p->file ) );
8421
					}
8422
				}
8423
8424
				self::$_auto_updated_plugins = $identifiers;
8425
			}
8426
8427
			if ( in_array( $by_slug ? $item->slug : $item, self::$_auto_updated_plugins ) ) {
8428
				return true;
8429
			}
8430
8431
			// Pass update decision to next filters
8432
			return $update;
8433
		}
8434
8435
		#region Versioning ------------------------------------------------------------------
8436
8437
		/**
8438
		 * Check if Freemius in SDK upgrade mode.
8439
		 *
8440
		 * @author Vova Feldman (@svovaf)
8441
		 * @since  1.0.9
8442
		 *
8443
		 * @return bool
8444
		 */
8445
		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...
8446
			return isset( $this->_storage->sdk_upgrade_mode ) ?
8447
				$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...
8448
				false;
8449
		}
8450
8451
		/**
8452
		 * Turn SDK upgrade mode off.
8453
		 *
8454
		 * @author Vova Feldman (@svovaf)
8455
		 * @since  1.0.9
8456
		 *
8457
		 * @return bool
8458
		 */
8459
		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...
8460
			$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...
8461
		}
8462
8463
		/**
8464
		 * Check if plugin upgrade mode.
8465
		 *
8466
		 * @author Vova Feldman (@svovaf)
8467
		 * @since  1.0.9
8468
		 *
8469
		 * @return bool
8470
		 */
8471
		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...
8472
			return isset( $this->_storage->plugin_upgrade_mode ) ?
8473
				$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...
8474
				false;
8475
		}
8476
8477
		/**
8478
		 * Turn plugin upgrade mode off.
8479
		 *
8480
		 * @author Vova Feldman (@svovaf)
8481
		 * @since  1.0.9
8482
		 *
8483
		 * @return bool
8484
		 */
8485
		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...
8486
			$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...
8487
		}
8488
8489
		#endregion ------------------------------------------------------------------
8490
8491
		#region Permissions ------------------------------------------------------------------
8492
8493
		/**
8494
		 * Check if specific permission requested.
8495
		 *
8496
		 * @author Vova Feldman (@svovaf)
8497
		 * @since  1.1.6
8498
		 *
8499
		 * @param string $permission
8500
		 *
8501
		 * @return bool
8502
		 */
8503
		function is_permission_requested( $permission ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

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

Loading history...
8504
			return isset( $this->_permissions[ $permission ] ) && ( true === $this->_permissions[ $permission ] );
8505
		}
8506
8507
		#endregion Permissions ------------------------------------------------------------------
8508
8509
		#region Marketing ------------------------------------------------------------------
8510
8511
		/**
8512
		 * Check if current user purchased any other plugins before.
8513
		 *
8514
		 * @author Vova Feldman (@svovaf)
8515
		 * @since  1.0.9
8516
		 *
8517
		 * @return bool
8518
		 */
8519
		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...
8520
			// TODO: Implement has_purchased_before() method.
8521
		}
8522
8523
		/**
8524
		 * Check if current user classified as an agency.
8525
		 *
8526
		 * @author Vova Feldman (@svovaf)
8527
		 * @since  1.0.9
8528
		 *
8529
		 * @return bool
8530
		 */
8531
		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...
8532
			// TODO: Implement is_agency() method.
8533
		}
8534
8535
		/**
8536
		 * Check if current user classified as a developer.
8537
		 *
8538
		 * @author Vova Feldman (@svovaf)
8539
		 * @since  1.0.9
8540
		 *
8541
		 * @return bool
8542
		 */
8543
		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...
8544
			// TODO: Implement is_developer() method.
8545
		}
8546
8547
		/**
8548
		 * Check if current user classified as a business.
8549
		 *
8550
		 * @author Vova Feldman (@svovaf)
8551
		 * @since  1.0.9
8552
		 *
8553
		 * @return bool
8554
		 */
8555
		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...
8556
			// TODO: Implement is_business() method.
8557
		}
8558
8559
		#endregion ------------------------------------------------------------------
8560
	}